refactor: migrate from astro to svelte + vite to improve performance (#131)

* add highlighter
* add playground url
* add markdown support and notification center
* fix multiple md files
* add meta description with framework list
* fix: framework id loop index
* store frameworkIdsSelected in locale storage
* add generateFrameworkContent vite plugin
* add missing snippet case
* add content generate cache
* add angular component highlighter
* improve content generator organization
* add format and linter
* add git hooks
* add default frameworks
This commit is contained in:
Mathieu Schimmerling
2023-01-03 01:36:47 +01:00
committed by GitHub
parent 4526ef2092
commit be52cbcbfc
63 changed files with 4373 additions and 3802 deletions

View File

@@ -1,4 +1,4 @@
import FRAMEWORKS from "./src/frameworks.mjs";
import FRAMEWORKS from "./frameworks.mjs";
/**
* @type {import("eslint").Linter.Config}

View File

@@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
node-version: [14.x, 16.x]
node-version: [16.x, 18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:

29
.gitignore vendored
View File

@@ -1,9 +1,26 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
new-section
.eslintcache
yarn.lock
package-lock.json
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
.history
.chrome
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
src/generatedContent

View File

@@ -1,2 +0,0 @@
tasks:
- init: pnpm install --frozen-lockfile

1
.husky/.gitignore vendored
View File

@@ -1 +0,0 @@
_

View File

@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

2
.npmrc
View File

@@ -1,2 +0,0 @@
# Expose Astro dependencies for `pnpm` users
shamefully-hoist=true

15
.vscode/settings.json vendored
View File

@@ -1,21 +1,8 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.probe": ["javascript", "javascriptreact", "vue"],
"editor.formatOnSave": false,
// Runs Prettier, then ESLint
"editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"],
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode"
},
"cSpell.words": [
"alpinejs",
"astro",
"astrojs",
"matschik",
"mdast",
"pnpm",
"qwik",
"shiki",
"webp"
]
}
}

View File

@@ -7,7 +7,7 @@ This site is built with [Astro](https://docs.astro.build). Site content is writt
1. Fork the project and create a new branch
2. Add the new framework SVG logo in `public/framework`
3. Install the ESLint plugin associated to the framework
4. In `src/frameworks.mjs`, add a new entry with SVG link and ESLint configuration
4. In `frameworks.mjs`, add a new entry with SVG link and ESLint configuration
## Improve website

21
LICENSE
View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2022-present, Mathieu Schimmerling
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,5 +1,3 @@
[![Open in Gitpod][gitpod-src]][gitpod-href]
![Component Party 🎉](.github/banner.webp)
> Web component JS frameworks quick overview by their syntax and features
@@ -383,7 +381,7 @@ This project requires Node.js to be `v14.0.0` or higher, because we use new Java
1. Fork the project and create a new branch
2. Add the new framework SVG logo in `public/framework`
3. Install the ESLint plugin associated to the framework
4. In `src/frameworks.mjs`, add a new entry with SVG link and ESLint configuration
4. In `frameworks.mjs`, add a new entry with SVG link and ESLint configuration
## 🧑‍💻 Contributors
@@ -393,8 +391,3 @@ This project exists thanks to all the people who contribute. \[[Contribute](CONT
## ⚖️ License
MIT. Made with 💖
<!-- variables -->
[gitpod-src]: https://shields.io/badge/Open%20in-Gitpod-green?logo=Gitpod
[gitpod-href]: https://gitpod.io/#https://github.com/matschik/component-party

View File

@@ -1,16 +0,0 @@
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import svelte from "@astrojs/svelte";
import FullReload from "vite-plugin-full-reload";
// https://astro.build/config
export default defineConfig({
// https://docs.astro.build/en/reference/configuration-reference/
integrations: [tailwind(), svelte()],
vite: {
plugins: [FullReload(["content/**/*"])],
optimizeDeps: {
exclude: ["locate-path", "path-exists", "find-up"],
},
},
});

View File

@@ -0,0 +1,43 @@
import fs from "fs";
import generateContent from "./lib/generateContent.js";
import { createFsCache } from "micache";
import { hashElement } from "folder-hash";
const contentDirFsCache = await createFsCache("pluginGenerateFrameworkContent");
export default function pluginGenerateFrameworkContent() {
const name = "generateFrameworkContent";
function logInfo(...args) {
console.info(`[${name}]`, ...args);
}
async function build() {
logInfo("Generating framework content files...");
const contentDirHash =
(await hashElement("content")).hash + (await hashElement("build")).hash;
const contentDirLastHash = await contentDirFsCache.get("contentDirHash");
if (contentDirHash !== contentDirLastHash) {
await generateContent();
await contentDirFsCache.set("contentDirHash", contentDirHash);
logInfo(`done`);
} else {
logInfo(`done with cache`);
}
}
let fsContentWatcher;
if (process.env.NODE_ENV === "development") {
fsContentWatcher = fs.watch("content", { recursive: true }, build);
}
return {
name,
async buildStart() {
await build();
},
buildEnd() {
fsContentWatcher && fsContentWatcher.close();
},
};
}

63
build/lib/angularHighlighter.js vendored Normal file
View File

@@ -0,0 +1,63 @@
export function mustUseAngularHighlighter(fileContent) {
return (
fileContent.includes("@angular/core") && fileContent.includes("template")
);
}
export function highlightAngularComponent(highlighter, fileContent, fileExt) {
const templateCode = getAngularTemplateCode(fileContent);
let codeHighlighted = "";
if (templateCode) {
const componentWithEmptyTemplate =
removeAngularTemplateContent(fileContent);
const templateCodeHighlighted = highlighter(templateCode, {
lang: "html",
});
const componentWithoutTemplateHighlighted = highlighter(
componentWithEmptyTemplate,
{
lang: fileExt,
}
);
codeHighlighted = componentWithoutTemplateHighlighted.replace(
"template",
"template: `" + removeCodeWrapper(templateCodeHighlighted) + "`,"
);
} else {
codeHighlighted = highlighter(fileContent, {
lang: fileExt,
});
}
return codeHighlighted;
}
function getAngularTemplateCode(fileContent) {
// regex to grab what is inside angular component template inside backticks
const regex = /template:\s*`([\s\S]*?)`/gm;
// grab the template string
const template = regex.exec(fileContent);
if (template) return template[1];
return "";
}
function removeAngularTemplateContent(fileContent) {
const componentWithoutContentInsideTemplate = fileContent.replace(
/template:\s*`([\s\S]*?)([^*])`,?/gm,
"template"
);
return componentWithoutContentInsideTemplate;
}
function removeCodeWrapper(html) {
const regexForWrapper = /<pre([\s\S]*?)><code>([\s\S]*?)<\/code><\/pre>/gm;
const code = regexForWrapper.exec(html);
return code[2];
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,194 @@
import fs from "node:fs/promises";
import { packageDirectory } from "pkg-dir";
import path from "node:path";
import kebabCase from "lodash.kebabcase";
import { getHighlighter } from "shiki";
import FRAMEWORKS from "../../frameworks.mjs";
import frameworkPlayground from "./playground/index.js";
import componentPartyShikiTheme from "./componentPartyShikiTheme.js";
import prettier from "prettier";
import markdownToHtml from "./markdownToHtml.js";
import {
highlightAngularComponent,
mustUseAngularHighlighter,
} from "./angularHighlighter.js";
export default async function generateContent() {
const highlighter = await getHighlighter({
theme: componentPartyShikiTheme,
langs: ["javascript", "svelte", "html", "hbs", "tsx", "jsx", "vue"],
});
const rootDir = await packageDirectory();
const contentPath = path.join(rootDir, "content");
const sectionDirNames = await fs.readdir(contentPath);
const treePayload = {
sections: [],
snippets: [],
};
const byFrameworkId = {};
for (const sectionDirName of sectionDirNames) {
const sectionTitle = dirNameToTitle(sectionDirName);
const sectionId = kebabCase(sectionTitle);
treePayload.sections.push({
sectionId,
sectionDirName,
title: sectionTitle,
});
const snippetsDirPath = path.join(contentPath, sectionDirName);
const snippetDirNames = await fs.readdir(snippetsDirPath);
for (const snippetDirName of snippetDirNames) {
const title = dirNameToTitle(snippetDirName);
const snippetId = kebabCase(title);
treePayload.snippets.push({
sectionId,
snippetId,
snippetDirName,
sectionDirName,
title,
});
const frameworksDirPath = path.join(snippetsDirPath, snippetDirName);
const frameworkIds = await fs.readdir(frameworksDirPath);
for (const frameworkId of frameworkIds) {
const frameworkSnippet = {
frameworkId,
snippetId,
files: [],
playgroundURL: "",
markdownFiles: [],
snippetEditHref: `https://github.com/matschik/component-party/tree/main/content/${sectionDirName}/${snippetDirName}/${frameworkId}`,
};
const codeFilesDirPath = path.join(frameworksDirPath, frameworkId);
const codeFileNames = await fs.readdir(codeFilesDirPath);
for (const codeFileName of codeFileNames) {
const codeFilePath = path.join(codeFilesDirPath, codeFileName);
const ext = path.parse(codeFilePath).ext.split(".").pop();
const content = await fs.readFile(codeFilePath, "utf-8");
const file = {
fileName: codeFileName,
ext,
content,
contentHtml: "",
};
if (ext === "md") {
file.contentHtml = await markdownToHtml(content);
frameworkSnippet.markdownFiles.push(file);
} else {
file.contentHtml = mustUseAngularHighlighter(content)
? highlightAngularComponent(highlighter.codeToHtml, content, ext)
: highlighter.codeToHtml(content, { lang: ext });
frameworkSnippet.files.push(file);
}
}
if (frameworkSnippet.files.length > 0) {
const playgroundURL = generatePlaygroundURL(
frameworkId,
frameworkSnippet.files
);
if (playgroundURL) {
frameworkSnippet.playgroundURL = playgroundURL;
}
}
if (!byFrameworkId[frameworkId]) {
byFrameworkId[frameworkId] = [];
}
byFrameworkId[frameworkId].push(frameworkSnippet);
}
}
}
const generatedContentDirPath = path.join(rootDir, "src/generatedContent");
const frameworkDirPath = path.join(generatedContentDirPath, "framework");
const treeFilePath = path.join(generatedContentDirPath, "tree.js");
const frameworkIndexPath = path.join(frameworkDirPath, "index.js");
await fs.rm(generatedContentDirPath, { recursive: true, force: true });
await fs.mkdir(generatedContentDirPath, { recursive: true });
const commentDisclaimer = `// File generated from "node scripts/generateContent.js", DO NOT EDIT`;
await writeJsFile(
treeFilePath,
`
${commentDisclaimer}
export const sections = ${JSON.stringify(treePayload.sections, null, 2)};
export const snippets = ${JSON.stringify(treePayload.snippets, null, 2)};
`
);
await fs.mkdir(frameworkDirPath, { recursive: true });
for (const frameworkId of Object.keys(byFrameworkId)) {
const frameworkFilePath = path.join(frameworkDirPath, `${frameworkId}.js`);
await writeJsFile(
frameworkFilePath,
`
${commentDisclaimer}
export default ${JSON.stringify(byFrameworkId[frameworkId], null, 2)}
`
);
}
await writeJsFile(
frameworkIndexPath,
`
${commentDisclaimer}
export default {
${Object.keys(byFrameworkId)
.map(
(frameworkId) =>
`${frameworkId}: () => import("./${frameworkId}.js")`
)
.join(",\n")}
};
`
);
}
function dirNameToTitle(dirName) {
return capitalize(dirName.split("-").splice(1).join(" "));
}
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
async function writeJsFile(filepath, jsCode) {
await fs.writeFile(filepath, prettier.format(jsCode, { parser: "babel" }));
}
function generatePlaygroundURL(frameworkId, files) {
const frameworkIdPlayground = frameworkPlayground[frameworkId];
if (!frameworkIdPlayground) {
return;
}
const frameworkConfig = FRAMEWORKS.find((f) => f.id === frameworkId);
const contentByFilename = frameworkConfig
.filesSorter(files)
.reduce((acc, file) => {
acc[file.fileName] = file.content;
return acc;
}, {});
const playgroundURL =
frameworkIdPlayground.fromContentByFilename(contentByFilename);
return playgroundURL;
}

View File

@@ -0,0 +1,29 @@
import withShiki from "@stefanprobst/remark-shiki";
import fromMarkdown from "remark-parse";
import * as shiki from "shiki";
import { unified } from "unified";
import toHast from "remark-rehype";
import withHtmlInMarkdown from "rehype-raw";
import toHtml from "rehype-stringify";
import componentPartyShikiTheme from "./componentPartyShikiTheme.js";
export default async function markdownToHtml(code) {
async function createProcessor() {
const highlighter = await shiki.getHighlighter({
theme: componentPartyShikiTheme,
});
const processor = unified()
.use(fromMarkdown)
.use(withShiki, { highlighter })
.use(toHast, { allowDangerousHtml: true })
.use(withHtmlInMarkdown)
.use(toHtml);
return processor;
}
const processor = await createProcessor();
const vfile = await processor.process(code);
return String(vfile);
}

View File

@@ -1,4 +1,4 @@
import nodePath from "path";
import nodePath from "node:path";
import { compressToURL } from "@matschik/lz-string";
export default () => {

View File

@@ -1,4 +1,4 @@
import nodePath from "path";
import nodePath from "node:path";
export default function createSvelteREPL() {
const BASE_URL = "https://svelte-repl.vercel.app/#";

View File

@@ -0,0 +1,11 @@
import createAlpinePlayground from "./createAlpinePlayground.js";
import createSveltePlayground from "./createSveltePlayground.js";
import createVue3Playground from "./createVue3Playground.js";
import createSolidPlayground from "./createSolidPlayground.js";
export default {
vue3: createVue3Playground(),
svelte: createSveltePlayground(),
alpine: createAlpinePlayground(),
solid: createSolidPlayground(),
};

52
index.html Normal file
View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width" />
<title>Component Party</title>
<link rel="icon" href="/favicon.png" />
<link
rel="preload"
href="/font/Mona-Sans.woff2"
as="font"
type="font/woff2"
crossorigin=""
/>
<meta
name="description"
content="Web component JS frameworks overview by their syntax and features: <%= frameworkList %>"
/>
<!-- Primary Meta Tags -->
<meta name="title" content="Component Party" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://component-party.dev/" />
<meta property="og:title" content="Component Party" />
<meta
property="og:description"
content="Web component JS frameworks overview by their syntax and features: <%= frameworkList %>"
/>
<meta
property="og:image"
content="https://component-party.dev/banner2.png"
/>
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://component-party.dev/" />
<meta property="twitter:title" content="Component Party" />
<meta
property="twitter:description"
content="Web component JS frameworks overview by their syntax and features: <%= frameworkList %>"
/>
<meta
property="twitter:image"
content="https://component-party.dev/banner2.png"
/>
</head>
<body class="bg-gray-900 text-white">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

33
jsconfig.json Normal file
View File

@@ -0,0 +1,33 @@
{
"compilerOptions": {
"moduleResolution": "Node",
"target": "ESNext",
"module": "ESNext",
/**
* svelte-preprocess cannot figure out whether you have
* a value or a type, so tell TypeScript to enforce using
* `import type` instead of `import` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
* To have warnings / errors of the Svelte compiler at the
* correct position, enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable this if you'd like to use dynamic types.
*/
"checkJs": true
},
/**
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

View File

@@ -1,87 +1,83 @@
{
"name": "component-party",
"version": "1.0.0",
"description": "Web component JS frameworks overview by their syntax and features",
"name": "component-party2",
"private": true,
"version": "0.0.0",
"type": "module",
"packageManager": "pnpm@7.0.0",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"progression": "node scripts/progress.js",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prettier": "prettier --ignore-path .gitignore --plugin-search-dir=. . --check",
"prettier:fix": "prettier --ignore-path .gitignore --plugin-search-dir=. --write .",
"format": "pnpm run prettier:fix && pnpm run lint:fix",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --fix",
"lint:check": "eslint .",
"format": "prettier --ignore-path .gitignore --plugin-search-dir=. --write .",
"format:check": "prettier --ignore-path .gitignore --plugin-search-dir=. . --check",
"build:content": "node scripts/generateContent.js",
"build:progress": "node scripts/generateReadMeProgress.js",
"prepare": "husky install"
},
"repository": {
"type": "git",
"url": "git+https://github.com/matschik/component-party.git"
"dependencies": {
"@veljs/svelte": "^0.1.8",
"classnames": "^2.3.2",
"heroiconsvelte": "^0.1.5"
},
"license": "MIT",
"bugs": {
"url": "https://github.com/matschik/component-party/issues"
},
"homepage": "https://github.com/matschik/component-party#readme",
"devDependencies": {
"@angular-eslint/eslint-plugin": "^14.1.2",
"@angular-eslint/eslint-plugin-template": "^14.1.2",
"@angular-eslint/template-parser": "^14.1.2",
"@angular/core": "^14.2.10",
"@angular/router": "^14.2.10",
"@astrojs/markdown-component": "^1.0.2",
"@astrojs/svelte": "^1.0.2",
"@astrojs/tailwind": "^2.1.2",
"@angular-eslint/eslint-plugin": "^15.1.0",
"@angular-eslint/eslint-plugin-template": "^15.1.0",
"@angular-eslint/template-parser": "^15.1.0",
"@babel/core": "^7.20.7",
"@babel/eslint-parser": "^7.19.1",
"@babel/plugin-proposal-decorators": "^7.20.2",
"@builder.io/qwik": "0.13.3",
"@babel/plugin-proposal-decorators": "^7.20.7",
"@matschik/lz-string": "^0.0.2",
"@stefanprobst/remark-shiki": "^2.2.0",
"@sveltejs/vite-plugin-svelte": "^2.0.2",
"@tailwindcss/typography": "^0.5.8",
"@types/mdast": "^3.0.10",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
"astro": "^1.6.7",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"autoprefixer": "^10.4.13",
"codesandbox": "^2.2.3",
"eslint": "^8.27.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-ember": "^11.2.0",
"eslint-plugin-lit": "^1.6.1",
"eslint": "^8.31.0",
"eslint-plugin-ember": "^11.4.0",
"eslint-plugin-lit": "^1.7.2",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-qwik": "^0.13.3",
"eslint-plugin-react": "^7.31.10",
"eslint-plugin-solid": "^0.8.0",
"eslint-plugin-qwik": "^0.16.1",
"eslint-plugin-react": "^7.31.11",
"eslint-plugin-solid": "^0.9.1",
"eslint-plugin-svelte3": "^4.0.0",
"eslint-plugin-vue": "^9.7.0",
"eslint-plugin-vue": "^9.8.0",
"esm": "^3.2.25",
"folder-hash": "^4.0.2",
"husky": "^8.0.2",
"lint-staged": "^13.0.3",
"lint-staged": "^13.1.0",
"lodash.kebabcase": "^4.1.1",
"micache": "^2.4.1",
"pkg-dir": "^7.0.0",
"postcss": "^8.4.19",
"prettier": "^2.7.1",
"prettier-plugin-astro": "^0.7.0",
"prettier-plugin-svelte": "^2.8.0",
"prop-types": "^15.8.1",
"postcss": "^8.4.20",
"prettier": "^2.8.1",
"prettier-plugin-svelte": "^2.9.0",
"react": "^18.2.0",
"rehype-raw": "^6.1.1",
"rehype-stringify": "^9.0.3",
"remark": "^14.0.2",
"shiki": "^0.11.1",
"svelte": "^3.53.1",
"remark-parse": "^10.0.1",
"remark-rehype": "^10.1.0",
"shiki": "^0.12.1",
"svelte": "^3.55.0",
"svelte-preprocess": "^5.0.0",
"tailwindcss": "^3.2.4",
"typescript": "^4.8.4",
"vite": "^3.2.3",
"vite-plugin-full-reload": "^1.0.4"
"typescript": "^4.9.4",
"unified": "^10.1.2",
"vite": "^4.0.3",
"vite-plugin-html": "^3.2.0"
},
"overrides": {
"@stefanprobst/remark-shiki": {
"shiki": "0.12.1"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,vue,svelte}": [
"prettier --write",
"eslint --cache --fix"
]
},
"dependencies": {
"alpinejs": "^3.10.5"
}
}

3639
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
postcss.config.cjs Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};

BIN
public/font/Mona-Sans.woff2 Normal file

Binary file not shown.

View File

@@ -0,0 +1,3 @@
import generateContent from "../build/lib/generateContent.js";
await generateContent();

View File

@@ -1,9 +1,9 @@
import { remark } from "remark";
import fs from "fs/promises";
import frameworks from "../src/frameworks.mjs";
import { packageDirectory } from "pkg-dir";
import nodePath from "node:path";
import kebabCase from "lodash.kebabcase";
import FRAMEWORKS from "../frameworks.mjs";
function removeMarkdownHeadingContent(
content,
@@ -127,7 +127,7 @@ async function main() {
);
let progressionContent = "";
for (const framework of frameworks) {
for (const framework of FRAMEWORKS) {
function mdCheck(b) {
return b ? "x" : " ";
}
@@ -167,5 +167,4 @@ ${list}
await fs.writeFile("README.md", newContent);
}
main().catch(console.error);

281
src/App.svelte Normal file
View File

@@ -0,0 +1,281 @@
<script>
import FRAMEWORKS from "../frameworks.mjs";
import c from "classnames";
import FrameworkLabel from "./components/FrameworkLabel.svelte";
import { sections, snippets } from "./generatedContent/tree.js";
import snippetsImporterByFrameworkId from "./generatedContent/framework/index.js";
import {
EyeSlashIcon,
PlayIcon,
PencilIcon,
ArrowUpIcon,
} from "heroiconsvelte/24/outline";
import CodeEditor from "./components/CodeEditor.svelte";
import AppNotificationCenter from "./components/AppNotificationCenter.svelte";
import createLocaleStorage from "./lib/createLocaleStorage.js";
import { onMount } from "svelte";
import Header from "./components/Header.svelte";
import Aside from "./components/Aside.svelte";
import GithubIcon from "./components/GithubIcon.svelte";
const frameworkIdsSelectedStorage = createLocaleStorage("framework_display");
let frameworkIdsSelected = new Set();
let snippetsByFrameworkId = new Map();
let frameworkIdsSelectedStorageInitialized = false;
onMount(() => {
frameworkIdsSelected = new Set(
frameworkIdsSelectedStorage.getJSON() || ["svelte", "react"]
);
frameworkIdsSelectedStorageInitialized = true;
});
$: {
if (frameworkIdsSelectedStorageInitialized) {
frameworkIdsSelectedStorage.setJSON([...frameworkIdsSelected]);
}
}
function hideFrameworkId(frameworkId) {
frameworkIdsSelected.delete(frameworkId);
frameworkIdsSelected = frameworkIdsSelected;
}
function toggleFrameworkId(frameworkId) {
if (frameworkIdsSelected.has(frameworkId)) {
frameworkIdsSelected.delete(frameworkId);
} else {
frameworkIdsSelected.add(frameworkId);
}
frameworkIdsSelected = frameworkIdsSelected;
}
$: {
async function importFrameworkSnippets(frameworkIds) {
for (const frameworkId of frameworkIds) {
if (!snippetsByFrameworkId.has(frameworkId)) {
const frameworkSnippets = (
await snippetsImporterByFrameworkId[frameworkId]()
).default;
snippetsByFrameworkId.set(frameworkId, frameworkSnippets);
}
}
snippetsByFrameworkId = snippetsByFrameworkId;
}
importFrameworkSnippets([...frameworkIdsSelected]);
}
</script>
<AppNotificationCenter />
<Header />
<div class="flex">
<Aside />
<div class="pb-8 w-full">
<div
class="flex px-6 lg:px-20 py-2 sticky top-0 z-10 w-full backdrop-blur bg-gray-900/80 border-b border-gray-700 whitespace-nowrap overflow-x-auto"
>
{#each FRAMEWORKS as framework}
<button
title={`Display ${framework.title}`}
class={c(
"text-sm flex-shrink-0 rounded border border-gray-700 px-3 py-1 border-opacity-50 bg-gray-900 hover:bg-gray-800 transition-all mr-2",
frameworkIdsSelected.has(framework.id)
? "border-blue-500"
: "opacity-70"
)}
on:click={() => toggleFrameworkId(framework.id)}
>
<FrameworkLabel id={framework.id} size={15} />
</button>
{/each}
</div>
<main id="main-content" class="px-6 md:px-14 lg:px-20 max-w-full pt-10">
<div>
{#if frameworkIdsSelected.size === 0}
<div class="space-y-4">
<div class="flex justify-center">
<ArrowUpIcon class="w-6 h-6 animate-bounce" />
</div>
<div class="flex justify-center">
<p
class="text-lg opacity-80 flex items-center text-center space-x-3"
>
<img src="/popper.svg" alt="logo" class="w-6 h-6" />
<span>
Please select a framework to view framework's snippets
</span>
<img src="/popper.svg" alt="logo" class="w-6 h-6" />
</p>
</div>
</div>
{:else}
<div
class="max-w-full prose prose-sm prose-invert prose-h1:scroll-mt-[5rem] prose-pre:mt-0 prose-h2:scroll-mt-[5rem]"
>
{#each sections as section}
<h1 id={section.sectionId} class="header-anchor">
{section.title}
<a
href={"#" + section.sectionId}
aria-hidden="true"
tabindex="-1"
>
#
</a>
</h1>
{#each snippets.filter((s) => s.sectionId === section.sectionId) as snippet}
<h2 id={snippet.snippetId} class="header-anchor">
{snippet.title}
<a
href={"#" + snippet.snippetId}
aria-hidden="true"
tabindex="-1"
>
#
</a>
</h2>
{#if frameworkIdsSelectedStorageInitialized}
<div
class="grid grid-cols-1 2xl:grid-cols-2 gap-10"
style="margin-top: 1rem;"
>
{#each [...frameworkIdsSelected] as frameworkId (frameworkId)}
{@const framework = FRAMEWORKS.find(
(f) => f.id === frameworkId
)}
{@const frameworkSnippet = snippetsByFrameworkId
.get(frameworkId)
?.find((s) => s.snippetId === snippet.snippetId)}
{#if frameworkSnippet}
<div style:margin-top="0rem" style:order="0">
<div
class="flex justify-between items-center space-x-3"
>
<h3 style="margin-top: 0rem; margin-bottom: 0rem;">
<FrameworkLabel id={framework.id} />
</h3>
<div class="flex items-center space-x-3">
{#if frameworkSnippet.playgroundURL}
<a
href={frameworkSnippet.playgroundURL}
target="_blank"
rel="noreferrer"
>
<button
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
title={`Open playground for ${framework.title}`}
aria-label={`Open playground for ${framework.title}`}
>
<PlayIcon class="h-4 w-4" />
</button>
</a>
{/if}
<a
href={frameworkSnippet.snippetEditHref}
target="_blank"
rel="noreferrer"
>
<button
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
title="Edit on Github"
aria-label="Edit on Github"
>
<PencilIcon class="h-4 w-4" />
</button>
</a>
<div>
<button
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
title={`Hide ${framework.title} snippets`}
aria-label={`Hide ${framework.title} snippets`}
on:click={() => hideFrameworkId(framework.id)}
>
<EyeSlashIcon class="h-4 w-4" />
</button>
</div>
</div>
</div>
<div class="mt-2">
{#if frameworkSnippet.markdownFiles.length > 0}
<div class="space-y-2">
{#each frameworkSnippet.markdownFiles as markdownFile}
<div>
{@html markdownFile.contentHtml}
</div>
{/each}
</div>
{:else if frameworkSnippet.files.length > 0}
<CodeEditor files={frameworkSnippet.files} />
{:else}
<div
class="bg-gray-800 text-white rounded-md mx-auto"
>
<div class="text-center py-8 px-4 sm:px-6">
<div>
<span
class="block text-2xl tracking-tight font-bold"
>
Missing snippet
</span>
<span
class="block text-lg mt-1 font-semibold"
>
Help us to improve Component Party 🎉
</span>
</div>
<div class="mt-6 flex justify-center">
<div class="inline-flex rounded-md shadow">
<a
class="inline-flex space-x-2 items-center justify-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-[#161b22] hover:bg-[#161b22]/80 no-underline"
href={frameworkSnippet.snippetEditHref}
>
<button
class="flex items-center space-x-3"
>
<span>Contribute on Github</span>
<GithubIcon class="h-5 w-5" />
</button>
</a>
</div>
</div>
</div>
</div>
{/if}
</div>
</div>
{/if}
{/each}
</div>
{/if}
{/each}
{/each}
</div>
{/if}
</div>
</main>
</div>
</div>
<style>
.header-anchor > a {
float: left;
margin-left: -0.87em;
padding-right: 0.23em;
font-weight: 500;
transition: color 0.25s, opacity 0.25s;
opacity: 0;
text-decoration: none;
}
.header-anchor:hover > a {
opacity: 100;
}
</style>

36
src/app.css Normal file
View File

@@ -0,0 +1,36 @@
:root {
color-scheme: dark;
}
@font-face {
font-family: "Mona Sans";
src: url("/font/Mona-Sans.woff2") format("woff2 supports variations"),
url("/font/Mona-Sans.woff2") format("woff2-variations");
font-weight: 200 500;
font-stretch: 75% 125%;
font-display: swap;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
font-family: Mona Sans;
@apply bg-gray-900;
}
/* #main-content .astro-code {
border-top-left-radius: 0px;
font-size: 1rem;
} */
@supports (backdrop-filter: blur(10px)) {
[class*="bg-"].backdrop-blur {
background-color: rgba(0, 0, 0, 0);
}
}
.shiki {
@apply text-[1.05rem];
}

View File

@@ -0,0 +1,40 @@
<script>
import NotificationCenter from "@veljs/svelte/NotificationCenter.svelte";
import TransitionWithClass from "@veljs/svelte/TransitionWithClass.svelte";
import { CheckCircleIcon, XMarkIcon } from "heroiconsvelte/24/outline";
</script>
<NotificationCenter zIndex={100} let:notification>
{@const { title } = notification}
<TransitionWithClass
class="pointer-events-auto overflow-hidden rounded-lg bg-[#181622] border border-[#33323e] shadow-lg ring-1 ring-black ring-opacity-5"
enter="transform ease-out duration-200 transition"
enterfrom="translate-y-2 opacity-0 translate-y-0 translate-x-2"
enterto="translate-y-0 opacity-100 translate-x-0"
leave="transition ease-in duration-100"
leavefrom="opacity-100"
leaveto="opacity-0"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<CheckCircleIcon class="h-6 w-6 text-green-400" />
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium">
{title}
</p>
</div>
<div class="ml-4 flex flex-shrink-0">
<button
class="inline-flex rounded-md bg-transparent focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
on:click={() => notification.close()}
>
<span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" />
</button>
</div>
</div>
</div>
</TransitionWithClass>
</NotificationCenter>

View File

@@ -0,0 +1,34 @@
<script>
import { sections, snippets } from "../generatedContent/tree.js";
</script>
<aside
class="hidden lg:block sticky flex-shrink-0 w-[300px] overflow-y-auto top-0 pr-8 max-h-screen border-r border-gray-700"
>
<nav class="font-semibold w-full text-base py-2 pl-4 pb-20">
<ul class="space-y-6">
{#each sections as section}
<li>
<a
href={`#${section.sectionId}`}
class="inline-block w-full py-1.5 px-4 text-white opacity-90 hover:opacity-100 hover:bg-gray-800 rounded transition-opacity"
>
{section.title}
</a>
<ul>
{#each snippets.filter((s) => s.sectionId === section.sectionId) as snippet}
<li>
<a
href={`#${snippet.snippetId}`}
class="inline-block w-full py-1.5 px-4 text-white opacity-50 hover:bg-gray-800 rounded hover:opacity-100 transition-opacity"
>
{snippet.title}
</a>
</li>
{/each}
</ul>
</li>
{/each}
</ul>
</nav>
</aside>

View File

@@ -0,0 +1,76 @@
<script>
import c from "classnames";
import { notifications } from "@veljs/svelte/NotificationCenter.svelte";
import { ClipboardDocumentIcon } from "heroiconsvelte/24/outline";
export let files = [];
let codeSnippetEl;
let filenameSelected = files.length > 0 && files[0]?.fileName;
$: snippet =
filenameSelected && files.find((s) => s.fileName === filenameSelected);
function copyToClipboard(value) {
const $textarea = document.createElement("textarea");
$textarea.innerHTML = value;
document.body.appendChild($textarea);
$textarea.select();
let success = false;
try {
document.execCommand("copy");
success = true;
} catch (err) {
alert(
"Oops, unable to copy to clipboard. Please check website permissions."
);
}
$textarea.remove();
return success;
}
function copySnippet() {
if (codeSnippetEl) {
copyToClipboard(codeSnippetEl.innerText);
notifications.show({
title: "Snippet copied to clipboard",
});
}
}
</script>
<div class="flex space-x-1 items-center ml-0 overflow-x-auto">
{#each files as file (file.fileName)}
<button
class={c(
"bg-[#0d1117] py-1.5 px-3 font-medium flex-shrink-0 text-xs rounded-t inline-block",
filenameSelected !== file.fileName && "opacity-60"
)}
on:click={() => {
filenameSelected = file.fileName;
}}
>
{file.fileName}
</button>
{/each}
</div>
<div class="relative group">
<div bind:this={codeSnippetEl} class="bg-[#0d1117]">
{@html snippet.contentHtml}
</div>
<div
class="absolute hidden group-hover:block transition-all top-0 right-0 mt-2 mr-2"
>
<div class="flex items-center space-x-3">
<button
class="px-1.5 bg-[#0d1117] py-1 rounded border opacity-60 hover:opacity-90"
title="Copy to clipboard"
aria-label="Copy to clipboard"
on:click={copySnippet}
>
<ClipboardDocumentIcon class="h-5 w-5" />
</button>
</div>
</div>
</div>

View File

@@ -1,24 +0,0 @@
import { getHighlighter as getShikiHighlighter } from "shiki";
// Caches Promise<Highligher> for reuse when the same theme and langs are provided
const _resolvedHighlighters = new Map();
function stringify(opts) {
// Always sort keys before stringifying to make sure objects match regardless of parameter ordering
return JSON.stringify(opts, Object.keys(opts).sort());
}
export function getHighlighter(opts) {
const key = stringify(opts);
// Highlighter has already been requested, reuse the same instance
if (_resolvedHighlighters.has(key)) {
return _resolvedHighlighters.get(key);
}
// Start the async getHighlighter call and cache the Promise
const highlighter = getShikiHighlighter(opts);
_resolvedHighlighters.set(key, highlighter);
return highlighter;
}

View File

@@ -1,42 +0,0 @@
import { getHighlighter } from "@/components/CodeHighlight/Shiki.js";
export default async function astroHighlightCode({
code,
lang = "plaintext",
theme = "github-dark",
wrap = false,
}) {
/** Replace the shiki class name with a custom astro class name. */
function repairShikiTheme(html) {
// Replace "shiki" class naming with "astro" and add "is:raw".
html = html.replace('<pre class="shiki"', '<pre is:raw class="astro-code"');
// Replace "shiki" css variable naming with "astro".
html = html.replace(
/style="(background-)?color: var\(--shiki-/g,
'style="$1color: var(--astro-code-'
);
// Handle code wrapping
// if wrap=null, do nothing.
if (wrap === false) {
html = html.replace(/style="(.*?)"/, 'style="$1; overflow-x: auto;"');
} else if (wrap === true) {
html = html.replace(
/style="(.*?)"/,
'style="$1; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;"'
);
}
return html;
}
const highlighter = await getHighlighter({
theme,
// Load custom lang if passed an object, otherwise load the default
langs: typeof lang !== "string" ? [lang] : undefined,
});
const _html = highlighter.codeToHtml(code, {
lang: typeof lang === "string" ? lang : lang.id,
});
const html = repairShikiTheme(_html);
return html;
}

View File

@@ -1,66 +0,0 @@
import astroHighlightCode from "@/components/CodeHighlight/astroHighlightCode.js";
export async function highlightAngularComponent(
fileContent,
theme,
wrap,
fileExt
) {
const templateCode = getAngularTemplateCode(fileContent);
if (!templateCode) return fileContent;
const componentWithEmptyTemplate = removeAngularTemplateContent(fileContent);
let templateCodeHighlighted = await astroHighlightCode({
code: templateCode,
lang: "html",
theme,
wrap,
});
let componentHighlighted = await astroHighlightCode({
code: componentWithEmptyTemplate,
lang: fileExt,
theme,
wrap,
});
return componentHighlighted.replace(
"template",
"template: `" + removeCodeWrapper(templateCodeHighlighted) + "`,"
);
}
function getAngularTemplateCode(fileContent) {
// regex to grab what is inside angular component template inside backticks
const regex = /template:\s*`([\s\S]*?)`/gm;
// grab the template string
const template = regex.exec(fileContent);
if (template) return template[1];
return "";
}
function removeAngularTemplateContent(fileContent) {
let componentWithoutContentInsideTemplate = fileContent.replace(
/template:\s*`([\s\S]*?)([^*])`,?/gm,
"template"
);
return componentWithoutContentInsideTemplate;
}
function removeCodeWrapper(html) {
let regexForWrapper = /<pre([\s\S]*?)><code>([\s\S]*?)<\/code><\/pre>/gm;
let code = regexForWrapper.exec(html);
return code[2];
}
export function isAngularComponent(fileContent) {
return (
fileContent.includes("@angular/core") && fileContent.includes("template")
);
}

View File

@@ -1,99 +0,0 @@
---
import FileCode from "@/components/FileCode.astro";
const { path, editHref } = Astro.props;
---
<script>
function copyToClipboard(value) {
const $textarea = document.createElement("textarea");
$textarea.innerHTML = value;
document.body.appendChild($textarea);
$textarea.select();
let success = false;
try {
document.execCommand("copy");
success = true;
} catch (err) {
alert(
"Oops, unable to copy to clipboard. Please check website permissions."
);
}
$textarea.remove();
return success;
}
for (const $codeCopyContainer of document.querySelectorAll(
"[data-code-copy-container]"
)) {
const $codeCopyText = $codeCopyContainer.querySelector(
"[data-code-copy-text]"
);
const $codeCopyButton = $codeCopyContainer.querySelector(
"[data-code-copy-button]"
);
$codeCopyButton.addEventListener("click", () => {
copyToClipboard($codeCopyText.innerText);
});
}
</script>
<div data-code-copy-container class="relative group">
<div data-code-copy-text>
<FileCode path={path} />
</div>
<div
class="absolute hidden group-hover:block transition-all top-0 right-0 mt-2 mr-2"
>
<div class="flex items-center space-x-3">
{
editHref && (
<a href={editHref} target="_blank">
<button
class="px-1.5 bg-[#0d1117] py-1 rounded border opacity-60 hover:opacity-90"
title="Edit on Github"
aria-label="Edit on Github"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
</button>
</a>
)
}
<button
data-code-copy-button
class="px-1.5 bg-[#0d1117] py-1 rounded border opacity-60 hover:opacity-90"
title="Copy to clipboard"
aria-label="Copy to clipboard"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
></path>
</svg>
</button>
</div>
</div>
</div>

View File

@@ -1,37 +0,0 @@
---
import fs from "node:fs/promises";
import nodePath from "node:path";
import { createFileMapCache } from "micache";
import astroHighlightCode from "@/components/CodeHighlight/astroHighlightCode.js";
import { highlightAngularComponent, isAngularComponent } from "@/components/CodeHighlight/highlightAngularComponent.js";
const { path, lang, theme = "github-dark", wrap = false } = Astro.props;
// check existence
await fs.access(path);
const fileCodeCache = await createFileMapCache("fileCodeCache");
let html = await fileCodeCache.get(path);
if (!html) {
const fileExt = nodePath.parse(path).ext.split(".").pop();
const fileContent = await fs.readFile(path, "utf-8");
if (isAngularComponent(fileContent)) {
html = await highlightAngularComponent(fileContent, theme, wrap, lang || fileExt);
} else {
html = await astroHighlightCode({
code: fileContent,
lang: lang || fileExt,
theme,
wrap,
});
}
fileCodeCache.set(path, html);
}
---
<Fragment set:html={html} />

View File

@@ -1,27 +0,0 @@
---
import FRAMEWORKS from "@/frameworks.mjs";
const { id, size = 20 } = Astro.props;
const framework = FRAMEWORKS.find((f) => f.id === id);
const baseURL =
import.meta.env.MODE === "development"
? ""
: "https://raw.githubusercontent.com/matschik/component-party/main/public/";
---
<div class="flex items-center space-x-1">
{
framework?.img && (
<img
src={baseURL + framework.img}
alt={framework.id}
width={size}
height={size}
class="inline mr-[5px] mb-0 mt-0"
/>
)
}
<span class="flex-shrink-0">{framework.title}</span>
</div>

View File

@@ -0,0 +1,24 @@
<script>
import FRAMEWORKS from "../../frameworks.mjs";
export let id;
export let size = 20;
const framework = FRAMEWORKS.find((f) => f.id === id);
const baseURL =
"https://raw.githubusercontent.com/matschik/component-party/main/public/";
</script>
<div class="flex items-center space-x-1">
{#if framework?.img}
<img
src={baseURL + framework.img}
alt={framework.id}
width={size}
height={size}
class="inline mr-[5px] mb-0 mt-0"
/>
{/if}
<span class="flex-shrink-0">{framework.title}</span>
</div>

View File

@@ -0,0 +1,12 @@
<svg
{...$$props}
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>

After

Width:  |  Height:  |  Size: 885 B

View File

@@ -0,0 +1,26 @@
<script>
import GithubIcon from "./GithubIcon.svelte";
</script>
<header class="backdrop-blur bg-gray-900/80 border-b border-gray-700">
<div class="px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center py-3">
<a class="font-semibold text-lg flex items-center space-x-3" href="/">
<img src="/popper.svg" alt="logo" class="w-5 h-5" />
<span>Component party</span>
</a>
<div>
<a
href="https://github.com/matschik/component-party"
title="Contribute on Github"
target="_blank"
rel="noopener noreferrer"
>
<GithubIcon class="h-6 w-6" />
<span class="sr-only">github</span>
</a>
</div>
</div>
</div>
</header>

View File

@@ -1,270 +0,0 @@
---
import fs from "node:fs/promises";
import kebabCase from "lodash.kebabcase";
import Markdown from "@astrojs/markdown-component";
import nodePath from "node:path";
import Title from "@/components/Title.svelte";
import FrameworkLabel from "@/components/FrameworkLabel.astro";
import FRAMEWORKS from "@/frameworks.mjs";
import CodeViewer from "@/components/CodeViewer.astro";
import repl from "@/utils/repl";
const { path: sectionPath } = Astro.props;
const sectionId = sectionPath.split("/").pop();
const sectionRelativePath = sectionPath.split("/").slice(-2).join("/");
const sections = [];
function dirNameToTitle(dirName) {
return capitalize(dirName.split("-").splice(1).join(" "));
}
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function generatePlaygroundURL(frameworkId, files) {
const frameworkREPL = repl[frameworkId];
if (!frameworkREPL) {
return;
}
const frameworkConfig = FRAMEWORKS.find((f) => f.id === frameworkId);
const contentByFilename = frameworkConfig
.filesSorter(files)
.reduce((acc, file) => {
acc[file.fileName] = file.content;
return acc;
}, {});
const playgroundURL = frameworkREPL.fromContentByFilename(contentByFilename);
return playgroundURL;
}
const subSectionDirNames = await fs.readdir(sectionPath);
for (const dirName of subSectionDirNames) {
const path = `${sectionPath}/${dirName}`;
const title = dirNameToTitle(dirName);
const frameworkDirs = await fs.readdir(path);
const frameworkSections = [];
for (const frameworkDir of frameworkDirs) {
const frameworkPath = `${path}/${frameworkDir}`;
const files = [];
const fileNames = await fs.readdir(`${frameworkPath}`);
for (const fileName of fileNames) {
const filePath = `${frameworkPath}/${fileName}`;
const ext = nodePath.parse(filePath).ext.split(".").pop();
files.push({
path: filePath,
fileName,
ext,
content: await fs.readFile(filePath, "utf-8"),
});
}
frameworkSections.push({
dirName: frameworkDir,
path: frameworkPath,
files,
playgroundURL: generatePlaygroundURL(frameworkDir, files),
});
}
sections.push({
id: kebabCase(title),
path,
dirName,
title,
frameworkSections,
});
}
function getSectionFiles(section, framework) {
return section.frameworkSections.find((f) => f.dirName === framework.id)
?.files;
}
function getSectionMarkdownFiles(section, framework) {
return getSectionFiles(section, framework)?.filter((f) => f.ext === "md");
}
function getSectionCodeFiles(section, framework) {
const files = getSectionFiles(section, framework)?.filter(
(f) => f.ext !== "md"
);
if (files?.length > 0 && typeof framework.filesSorter === "function") {
return framework.filesSorter(files);
} else {
return files;
}
}
function getFrameworkSectionPlaygroundURL(section, framework) {
return section.frameworkSections.find((f) => f.dirName === framework.id)
?.playgroundURL;
}
---
<Title as="h1" content={dirNameToTitle(sectionId)} />
{
sections.map((section) => (
<>
<Title as="h2" content={section.title} />
<div
class="grid grid-cols-1 2xl:grid-cols-2 gap-10"
style="margin-top: 1rem;"
>
{FRAMEWORKS.map((framework) => (
<div
x-bind:style={`'margin-top: 0rem;' + 'order: ' + $store.frameworksSelected.getOrder('${framework.id}') + ';' + 'display:' + ($store.frameworksSelected.has('${framework.id}') ? 'block' : 'none') + ';'`}
>
<div class="flex justify-between items-center space-x-3">
<h3 style="margin-top: 0rem; margin-bottom: 0rem;">
<FrameworkLabel id={framework.id} />
</h3>
<div class="flex items-center space-x-3">
{getFrameworkSectionPlaygroundURL(section, framework) && (
<a
href={getFrameworkSectionPlaygroundURL(section, framework)}
target="_blank"
>
<button
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
title={`Open playground for ${framework.title}`}
aria-label={`Open playground for ${framework.title}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</button>
</a>
)}
<button
class="opacity-50 hover:opacity-100 bg-gray-800 hover:bg-gray-700 py-1 px-1.5 rounded transition-all"
title={`Hide ${framework.title} snippets`}
aria-label={`Hide ${framework.title} snippets`}
x-on:click={`$store.frameworksSelected.hide('${framework.id}')`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
/>
</svg>
</button>
</div>
</div>
<div class="mt-2">
{getSectionMarkdownFiles(section, framework)?.length > 0 && (
<div>
{getSectionMarkdownFiles(section, framework).map((file) => (
<Markdown set:html={file.content || ""} />
))}
</div>
)}
{getSectionCodeFiles(section, framework)?.length > 0 && (
<div
x-data={`{ filenameSelected: '${
getSectionCodeFiles(section, framework)[0].fileName
}' }`}
>
<div class="flex space-x-1 items-center ml-0 overflow-x-auto">
{getSectionCodeFiles(section, framework).map((file) => (
<button
class="bg-[#0d1117] py-1.5 px-3 font-medium flex-shrink-0 text-xs rounded-t inline-block"
x-bind:class={`filenameSelected === '${file.fileName}' ? '' : 'opacity-60'`}
x-on:click={`filenameSelected = '${file.fileName}'`}
>
{file.fileName}
</button>
))}
</div>
<div>
{getSectionCodeFiles(section, framework).map((file) => (
<div x-show={`filenameSelected === '${file.fileName}'`}>
<CodeViewer
path={file.path}
editHref={`https://github.com/matschik/component-party/tree/main/${sectionRelativePath}/${section.dirName}/${framework.id}/${file.fileName}`}
/>
</div>
))}
</div>
</div>
)}
{!getSectionFiles(section, framework)?.length && (
<div class="">
<div class="bg-gray-800 text-white rounded-md mx-auto">
<div class="text-center py-8 px-4 sm:px-6">
<div>
<span class="block text-2xl tracking-tight font-bold">
Missing snippet
</span>
<span class="block text-lg mt-1 font-semibold">
Help us to improve Component Party 🎉
</span>
</div>
<div class="mt-6 flex justify-center">
<div class="inline-flex rounded-md shadow">
<a
href={`https://github.com/matschik/component-party/tree/main/${sectionRelativePath}/${section.dirName}`}
class="inline-flex space-x-2 items-center justify-center px-4 py-2 border border-transparent text-base font-medium rounded-md text-white bg-[#161b22] hover:bg-[#161b22]/80 no-underline"
>
<span>Contribute on Github</span>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"
viewBox="0 0 24 24"
fill="currentColor"
class="h-5 w-5 text-white"
>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
</a>
</div>
</div>
</div>
</div>
</div>
)}
</div>
</div>
))}
</div>
</>
))
}

View File

@@ -1,16 +0,0 @@
<script>
import kebabCase from "lodash.kebabcase";
export let as = "h1";
export let content = "";
$: id = kebabCase(content);
</script>
<svelte:element this={as} {id}
>{content}<a
class="header-anchor"
href={"#" + id}
aria-hidden="true"
tabindex="-1">#</a
></svelte:element
>

View File

@@ -1,89 +0,0 @@
import Alpine from "alpinejs";
Alpine.store("frameworksSelected", {
_frameworksSelectedProxy: createLocaleStorageProxy("frameworks_display"),
_selectedIds: [],
init() {
// set default value of frameworksSelectedProxy
if (!Object.values(this._frameworksSelectedProxy).length) {
const initialFrameworkIds = ["react", "svelte"];
for (let i = 0; i < initialFrameworkIds.length; i++) {
this._frameworksSelectedProxy[i] = initialFrameworkIds[i];
}
}
this._selectedIds = [...this._frameworksSelectedProxy];
},
getOrder(fmwId) {
return this._selectedIds.indexOf(fmwId) + 1;
},
has(fmwId) {
return this._selectedIds.includes(fmwId);
},
toggle(fmwId) {
this.has(fmwId) ? this.hide(fmwId) : this.show(fmwId);
},
hide(fmwId) {
if (this.has(fmwId)) {
const frameworkIndex = this._frameworksSelectedProxy.indexOf(fmwId);
delete this._frameworksSelectedProxy[frameworkIndex];
}
this._selectedIds = [...this._frameworksSelectedProxy];
},
show(fmwId) {
if (!this.has(fmwId)) {
this._frameworksSelectedProxy.push(fmwId);
}
this._selectedIds = [...this._frameworksSelectedProxy];
},
});
Alpine.start();
function createLocaleStorageProxy(key) {
const storage = createLocaleStorage(key);
return new Proxy(storage.getJSON() || [], {
get(target, prop) {
return target[prop];
},
set(target, prop, value, receiver) {
target[prop] = value;
storage.setJSON(receiver);
return true;
},
deleteProperty(target, prop) {
target.splice(prop, 1);
storage.setJSON(target);
return true;
},
});
}
function createLocaleStorage(k) {
function get() {
return localStorage.getItem(k);
}
return {
get,
getJSON() {
var value = get();
if (value) {
try {
return JSON.parse(value);
} catch (err) {
console.error({ getJSONErr: err });
return undefined;
}
}
},
setJSON(o) {
this.set(JSON.stringify(o));
},
set(v) {
localStorage.setItem(k, v);
},
remove() {
localStorage.removeItem(k);
},
};
}

View File

@@ -1,42 +0,0 @@
---
import FRAMEWORKS from "@/frameworks.mjs";
import BaseLayout from "@/layouts/BaseLayout.svelte";
import generateContentTree from "@/utils/generateContentTree.js";
import FrameworkLabel from "@/components/FrameworkLabel.astro";
const tree = await generateContentTree();
---
<BaseLayout tree={tree}>
<script src="@/components/scripts/alpine-init.js"></script>
<div
x-init="() => { $el.classList.remove('hidden');$el.classList.add('block') }"
class="hidden"
>
<div
class="flex px-6 lg:px-20 py-2 sticky top-0 z-10 w-full backdrop-blur bg-gray-900/80 border-b border-gray-700 whitespace-nowrap overflow-x-auto"
>
{
FRAMEWORKS.map((framework) => (
<button
data-framework-button-show={framework.id}
title={`Display ${framework.title}`}
class="text-sm flex-shrink-0 rounded border border-gray-700 px-3 py-1 border-opacity-50 bg-gray-900 hover:bg-gray-800 transition-all mr-2"
x-bind:class={`$store.frameworksSelected.has('${framework.id}') ? 'border-blue-500' : 'opacity-70'`}
x-on:click={`$store.frameworksSelected.toggle('${framework.id}')`}
>
<FrameworkLabel id={framework.id} size={15} />
</button>
))
}
</div>
<main
id="main-content"
class="px-6 md:px-14 lg:px-20 pt-10 prose prose-sm prose-invert prose-h1:scroll-mt-[5rem] prose-pre:mt-0 prose-h2:scroll-mt-[5rem]"
style="max-width: 100%;"
>
<slot />
</main>
</div>
</BaseLayout>

View File

@@ -1,165 +0,0 @@
<script>
import FRAMEWORKS from "@/frameworks.mjs";
export let tree = [];
</script>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width" />
<title>Component Party</title>
<link rel="icon" href="/favicon.png" />
<link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
<meta
name="description"
content={`Web component JS frameworks overview by their syntax and features: ${FRAMEWORKS.map(
(f) => f.title
).join(", ")}`}
/>
<style>
* {
scrollbar-color: rgba(255, 255, 255, 0.25) rgba(0, 0, 0, 0);
scrollbar-width: thin;
}
::-webkit-scrollbar {
width: 4px;
height: 4px;
}
::-webkit-scrollbar-track {
background-color: rgba(0, 0, 0, 0);
}
::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.25);
}
#main-content h1:hover .header-anchor,
#main-content h2:hover .header-anchor {
opacity: 100;
}
#main-content .astro-code {
border-top-left-radius: 0px;
font-size: 1rem;
}
#main-content .header-anchor {
float: left;
margin-left: -0.87em;
padding-right: 0.23em;
font-weight: 500;
transition: color 0.25s, opacity 0.25s;
opacity: 0;
text-decoration: none;
}
@supports (backdrop-filter: blur(10px)) {
[class*="bg-"].backdrop-blur {
background-color: rgba(0, 0, 0, 0);
}
}
</style>
<!-- Primary Meta Tags -->
<meta name="title" content="Component Party" />
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta property="og:url" content="https://component-party.dev/" />
<meta property="og:title" content="Component Party" />
<meta
property="og:description"
content="Web component JS frameworks overview by their syntax and features: Svelte, React, Vue 3, Angular"
/>
<meta
property="og:image"
content="https://component-party.dev/banner2.png"
/>
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://component-party.dev/" />
<meta property="twitter:title" content="Component Party" />
<meta
property="twitter:description"
content="Web component JS frameworks overview by their syntax and features: Svelte, React, Vue 3, Angular"
/>
<meta
property="twitter:image"
content="https://component-party.dev/banner2.png"
/>
</head>
<body class="bg-gray-900 text-white font-sans">
<header class="backdrop-blur bg-gray-900/80 border-b border-gray-700">
<div class="px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center py-3">
<a
class="font-semibold text-lg flex items-center space-x-3"
href="#reactivity"
>
<img src="/popper.svg" alt="logo" class="w-5 h-5" />
<span>Component party</span>
</a>
<div>
<a
href="https://github.com/matschik/component-party"
title="Contribute on Github"
target="_blank"
rel="noopener noreferrer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
focusable="false"
viewBox="0 0 24 24"
fill="currentColor"
class="h-6 w-6 text-white"
>
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
/>
</svg>
<span class="sr-only">github</span>
</a>
</div>
</div>
</div>
</header>
<div class="flex">
<aside
class="hidden lg:block sticky flex-shrink-0 w-[300px] overflow-y-auto top-0 pr-12 max-h-screen border-r border-gray-700"
>
<nav class="font-semibold w-full text-base py-2 pl-4 pb-20">
<ul class="space-y-6">
{#each tree as treeNode}
<li>
<a
href={`#${treeNode.id}`}
class="inline-block w-full py-1.5 px-4 text-white opacity-90 hover:opacity-100 hover:bg-gray-800 rounded transition-opacity"
>
{treeNode.title}
</a>
<ul>
{#each treeNode.sections as section}
<li>
<a
href={`#${section.id}`}
class="inline-block w-full py-1.5 px-4 text-white opacity-50 hover:bg-gray-800 rounded hover:opacity-100 transition-opacity"
>
{section.title}
</a>
</li>
{/each}
</ul>
</li>
{/each}
</ul>
</nav>
</aside>
<div class="pb-8 w-full">
<slot />
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,28 @@
export default function createLocaleStorage(k) {
function get() {
return localStorage.getItem(k);
}
return {
get,
getJSON() {
var value = get();
if (value) {
try {
return JSON.parse(value);
} catch (err) {
console.error({ getJSONErr: err });
return undefined;
}
}
},
setJSON(o) {
this.set(JSON.stringify(o));
},
set(v) {
localStorage.setItem(k, v);
},
remove() {
localStorage.removeItem(k);
},
};
}

8
src/main.js Normal file
View File

@@ -0,0 +1,8 @@
import App from "./App.svelte";
import "./app.css";
const app = new App({
target: document.getElementById("app"),
});
export default app;

View File

@@ -1,21 +0,0 @@
---
import fs from "node:fs/promises";
import { packageDirectory } from "pkg-dir";
import Layout from "@/layouts/BaseLayout.astro";
import Section from "@/components/Section.astro";
const rootDir = await packageDirectory();
const contentURL = `${rootDir}/content`;
const sectionNames = await fs.readdir(contentURL);
---
<Layout>
<div class="space-y-12">
{
sectionNames.map((sectionName) => (
<Section path={`${contentURL}/${sectionName}`} />
))
}
</div>
</Layout>

View File

@@ -1,49 +0,0 @@
export default function createCache({ expireSec } = {}) {
const cache = {};
function setCache(key, data) {
cache[key] = {
created: new Date().getTime(),
expireSec,
data,
};
}
function getCache(key) {
if (
cache[key] &&
cache[key].expireSec &&
new Date().getTime() - cache[key].created >= cache[key].expireSec * 1000
) {
deleteKey(key);
}
return cache[key]?.data;
}
function hasKey(key) {
return cache.hasOwnProperty(key);
}
function deleteKey(key) {
// Remove the reference to the object on object `cache`.
// The v8 garbage collector will pick up any objects with zero references for garbage collection.
delete cache[key];
}
function cleanKeyStartsWith(keyStart, except) {
for (const key in cache) {
if (key.startsWith(keyStart) && except && key !== except) {
deleteKey(key);
}
}
}
return {
set: setCache,
get: getCache,
has: hasKey,
delete: deleteKey,
cleanKeyStartsWith,
};
}

View File

@@ -1,56 +0,0 @@
import fs from "node:fs/promises";
import crypto from "crypto";
import createFsCache from "@/utils/createFsCache.js";
async function md5(data) {
return crypto.createHash("md5").update(data).digest("hex");
}
async function fileExist(path) {
try {
await fs.access(path);
} catch {
return false;
}
return true;
}
export default async function createFileMapCache(name) {
const cache = await createFsCache(name);
async function _getPathHash(path) {
return md5(path);
}
async function _getCacheKey(path) {
const { mtimeMs } = await fs.stat(path);
const pathHash = await _getPathHash(path);
return `${pathHash}_${mtimeMs}`;
}
async function set(path, data) {
if (await fileExist(path)) {
const pathHash = await _getPathHash(path);
await cache.cleanKeyStartsWith(pathHash);
const cacheKey = await _getCacheKey(path);
await cache.set(cacheKey, data);
}
}
async function has(path) {
const cacheKey = await _getCacheKey(path);
return await cache.has(cacheKey);
}
async function get(path) {
const cacheKey = await _getCacheKey(path);
return await cache.get(cacheKey);
}
return {
set,
get,
has,
};
}

View File

@@ -1,66 +0,0 @@
import fs from "node:fs/promises";
import { packageDirectory } from "pkg-dir";
async function ensureDir(path) {
try {
await fs.access(path);
} catch (err) {
if (err.code === "ENOENT") {
await fs.mkdir(path).catch(() => {});
}
}
}
export default async function createFsCache(name) {
const pkgDir = await packageDirectory();
const fsCacheDir = `${pkgDir}/node_modules/.fs-cache`;
const cacheDir = `${fsCacheDir}/${name}`;
await ensureDir(fsCacheDir);
await ensureDir(cacheDir);
function getPath(key) {
return `${cacheDir}/${key}`;
}
async function set(key, data) {
await fs.writeFile(getPath(key), data);
}
async function get(key) {
if (await has(key)) {
return await fs.readFile(getPath(key), "utf8");
}
}
async function has(key) {
try {
await fs.access(getPath(key));
} catch {
return false;
}
return true;
}
async function deleteKey(key) {
if (await has(key)) {
return await fs.deleteFile(getPath(key));
}
}
async function cleanKeyStartsWith(keyStart, except) {
const files = await fs.readdir(cacheDir);
for (const file of files) {
if (file.startsWith(keyStart) && except && key !== except) {
deleteKey(file);
}
}
}
return {
set,
get,
has,
delete: deleteKey,
cleanKeyStartsWith,
};
}

View File

@@ -1,43 +0,0 @@
import fs from "node:fs/promises";
import path from "path";
const CONTENT_DIR = path.resolve("content");
export default async function generateContentTree() {
const tree = [];
const contentDirs = await fs.readdir(CONTENT_DIR);
for (const contentDir of contentDirs) {
const sectionDir = `${CONTENT_DIR}/${contentDir}`;
const subSectionDirs = (await fs.readdir(sectionDir)).filter(
(path) => !path.includes(".")
);
const contentDirTitle = dirNameToTitle(contentDir);
const treeNode = {
id: contentDir.split("-").splice(1).join("-"),
title: contentDirTitle,
sections: [],
};
for (const subSectionDir of subSectionDirs) {
const subSectionDirTitle = dirNameToTitle(subSectionDir);
treeNode.sections.push({
id: subSectionDir.split("-").splice(1).join("-"),
title: subSectionDirTitle,
});
}
tree.push(treeNode);
}
return tree;
}
function dirNameToTitle(dirName) {
return capitalize(dirName.split("-").splice(1).join(" "));
}
function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}

View File

@@ -1,11 +0,0 @@
import createAlpineREPL from "./createAlpineREPL";
import createSvelteREPL from "./createSvelteREPL";
import createVue3REPL from "./createVue3REPL";
import createSolidREPL from "./createSolidREPL";
export default {
vue3: createVue3REPL(),
svelte: createSvelteREPL(),
alpine: createAlpineREPL(),
solid: createSolidREPL(),
};

View File

@@ -1,13 +1,4 @@
const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,svelte,ts,tsx,vue}"],
theme: {
extend: {
fontFamily: {
sans: ["Inter var", ...defaultTheme.fontFamily.sans],
},
},
},
content: ["./src/**/*.{html,js,jsx,svelte,ts,tsx,vue}", "./index.html"],
plugins: [require("@tailwindcss/typography")],
};

View File

@@ -1,10 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts", "src/polyfills.ts"],
"include": ["src/**/*.d.ts"]
}

View File

@@ -1,20 +0,0 @@
{
"compileOnSave": false,
"compilerOptions": {
"allowJs": true,
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2015",
"module": "es2020",
"lib": ["es2018", "dom"],
"paths": {
"@/*": ["src/*"]
}
}
}

View File

@@ -1,10 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": ["jasmine"]
},
"files": ["src/test.ts", "src/polyfills.ts"],
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
}

28
vite.config.js Normal file
View File

@@ -0,0 +1,28 @@
import { defineConfig } from "vite";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import sveltePreprocess from "svelte-preprocess";
import { createHtmlPlugin } from "vite-plugin-html";
import FRAMEWORKS from "./frameworks.mjs";
import pluginGenerateFrameworkContent from "./build/generateContentVitePlugin.js";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
pluginGenerateFrameworkContent(),
svelte(),
createHtmlPlugin({
minify: true,
inject: {
data: {
frameworkList: FRAMEWORKS.map((f) => f.title).join(", "),
},
},
}),
],
ignore: ["content"],
preprocess: [
sveltePreprocess({
postcss: true,
}),
],
});