diff --git a/bun.lockb b/bun.lockb index ca1e6ba8..b1dfb957 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/qwik-web/README.md b/packages/qwik-web/README.md index 482a769f..bb4b7d0e 100644 --- a/packages/qwik-web/README.md +++ b/packages/qwik-web/README.md @@ -63,3 +63,9 @@ The production build will generate client and server modules by running both cli ```shell bun build # or `bun build` ``` + +## Static Site Generator (Node.js) + +```shell +bun build.server +``` diff --git a/packages/qwik-web/adapters/static/vite.config.ts b/packages/qwik-web/adapters/static/vite.config.ts new file mode 100644 index 00000000..691408e4 --- /dev/null +++ b/packages/qwik-web/adapters/static/vite.config.ts @@ -0,0 +1,19 @@ +import { staticAdapter } from "@builder.io/qwik-city/adapters/static/vite"; +import { extendConfig } from "@builder.io/qwik-city/vite"; +import baseConfig from "../../vite.config"; + +export default extendConfig(baseConfig, () => { + return { + build: { + ssr: true, + rollupOptions: { + input: ["@qwik-city-plan"], + }, + }, + plugins: [ + staticAdapter({ + origin: "https://yoursite.qwik.dev", + }), + ], + }; +}); diff --git a/packages/qwik-web/bun.lockb b/packages/qwik-web/bun.lockb new file mode 100755 index 00000000..91e555e5 Binary files /dev/null and b/packages/qwik-web/bun.lockb differ diff --git a/packages/qwik-web/package.json b/packages/qwik-web/package.json index 12e75661..1967a427 100644 --- a/packages/qwik-web/package.json +++ b/packages/qwik-web/package.json @@ -15,25 +15,28 @@ "build": "qwik build", "build.client": "vite build", "build.preview": "vite build --ssr src/entry.preview.tsx", + "build.server": "vite build -c adapters/static/vite.config.ts", "build.types": "tsc --incremental --noEmit", + "build.velite": "velite build", "deploy": "echo 'Run \"npm run qwik add\" to install a server adapter'", "dev": "vite --mode ssr", "dev.debug": "node --inspect-brk ./node_modules/vite/bin/vite.js --mode ssr --force", + "dev.velite": "bunx --bun velite dev", "fmt": "prettier --write .", "fmt.check": "prettier --check .", "lint": "eslint \"src/**/*.ts*\"", "preview": "qwik build preview && vite preview --open", "start": "vite --open --mode ssr", - "qwik": "qwik", - "dev.velite": "bunx --bun velite dev" + "qwik": "qwik" }, "devDependencies": { - "@builder.io/qwik": "^1.6.0", - "@builder.io/qwik-city": "^1.6.0", + "@builder.io/qwik": "^1.5.7", + "@builder.io/qwik-city": "^1.5.7", "@types/eslint": "^8.56.10", "@types/node": "^20.12.7", "@typescript-eslint/eslint-plugin": "^7.7.1", "@typescript-eslint/parser": "^7.7.1", + "@types/remark-sectionize": "workspace:../remark-sectionize", "eslint": "^8.57.0", "eslint-plugin-qwik": "^1.6.0", "prettier": "^3.2.5", @@ -43,6 +46,15 @@ "vite-tsconfig-paths": "^4.2.1" }, "dependencies": { + "@types/mdast": "^4.0.4", + "rehype-pretty-code": "^0.13.2", + "remark-gemoji": "^8.0.0", + "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", + "remark-parse": "^11.0.0", + "remark-sectionize": "^2.0.0", + "shiki": "^1.10.1", + "unified": "^11.0.5", "velite": "^0.1.0" } } diff --git a/packages/qwik-web/src/components/md/code-block.tsx b/packages/qwik-web/src/components/md/code-block.tsx new file mode 100644 index 00000000..f2e16eb6 --- /dev/null +++ b/packages/qwik-web/src/components/md/code-block.tsx @@ -0,0 +1,38 @@ +import { Resource, component$, useResource$ } from "@builder.io/qwik"; +import { + type BundledLanguage, + codeToHtml, + type StringLiteralUnion, + type SpecialLanguage, +} from "shiki"; + +export interface Props { + lang?: StringLiteralUnion; + text: string; +} + +export default component$((props: Props) => { + const codeResource = useResource$(async () => { + if (props.lang) { + return codeToHtml(props.text, { + lang: props.lang, + themes: { + dark: "github-dark-dimmed", + light: "github-light", + }, + }); + } else { + return `${props.text}`; + } + }); + return ( +
+
}
+        onResolved={(text) => 
} + onRejected={(err) =>
{JSON.stringify(err)}
} + > +
+ ); +}); diff --git a/packages/qwik-web/src/components/md/index.tsx b/packages/qwik-web/src/components/md/index.tsx new file mode 100644 index 00000000..4414e93c --- /dev/null +++ b/packages/qwik-web/src/components/md/index.tsx @@ -0,0 +1,21 @@ +import { component$ } from "@builder.io/qwik"; +import type * as MdAst from "mdast"; +import RootContent from "~/components/md/root-content"; + +export interface Props { + root: MdAst.Root; +} + +function key(base: unknown): string { + return JSON.stringify(base); +} + +export default component$((props: Props) => { + return ( +
+ {props.root.children.map((node) => ( + + ))} +
+ ); +}); diff --git a/packages/qwik-web/src/components/md/root-content.tsx b/packages/qwik-web/src/components/md/root-content.tsx new file mode 100644 index 00000000..66ca0eb6 --- /dev/null +++ b/packages/qwik-web/src/components/md/root-content.tsx @@ -0,0 +1,114 @@ +import { Link } from "@builder.io/qwik-city"; +import type * as MdAst from "mdast"; +import CodeBlock from "./code-block"; + +export function key(node: MdAst.RootContent): string { + return JSON.stringify(node.position); +} + +const RootContent = (props: { node: MdAst.RootContent }) => { + switch (props.node.type) { + case "blockquote": + return ( +
+ {props.node.children.map((node) => ( + + ))} +
+ ); + case "paragraph": + return ( +

+ {props.node.children.map((node) => ( + + ))} +

+ ); + case "section": + return ( +
+ {props.node.children.map((node) => ( + + ))} +
+ ); + case "text": + return props.node.value; + case "inlineCode": + return {props.node.value}; + case "heading": + const inner = props.node.children.map((node) => ( + + )); + switch (props.node.depth) { + case 1: + return

{inner}

; + case 2: + return

{inner}

; + case 3: + return

{inner}

; + case 4: + return

{inner}

; + case 5: + return
{inner}
; + case 6: + return
{inner}
; + } + return null; + case "break": + return
; + case "link": + return ( + + {props.node.children.map((node) => ( + + ))} + + ); + case "code": + return ( + + ); + case "list": + if (props.node.ordered) { + return ( +
    + {props.node.children.map((node) => ( + + ))} +
+ ); + } else { + return ( +
    + {props.node.children.map((node) => ( + + ))} +
+ ); + } + case "listItem": + return ( +
  • + {props.node.children.map((node) => ( + + ))} +
  • + ); + case "strong": + return ( + + {props.node.children.map((node) => ( + + ))} + + ); + default: + return JSON.stringify(props.node); + } +}; + +export default RootContent; diff --git a/packages/qwik-web/src/routes/[...slug]/index.tsx b/packages/qwik-web/src/routes/[...slug]/index.tsx deleted file mode 100644 index e69de29b..00000000 diff --git a/packages/qwik-web/src/routes/index.tsx b/packages/qwik-web/src/routes/index.tsx index 2ddd9d33..0f5e7a01 100644 --- a/packages/qwik-web/src/routes/index.tsx +++ b/packages/qwik-web/src/routes/index.tsx @@ -1,17 +1,23 @@ import { component$ } from "@builder.io/qwik"; import type { DocumentHead } from "@builder.io/qwik-city"; -import {post} from "#site/content"; -console.log(post); +import { post as posts } from "#site/content"; +import { Link } from "@builder.io/qwik-city"; export default component$(() => { return ( <>

    Hi 👋

    -
    - Can't wait to see what you build with qwik! -
    - Happy coding. -
    + ); }); diff --git a/packages/qwik-web/src/routes/post/[...slug]/index.tsx b/packages/qwik-web/src/routes/post/[...slug]/index.tsx new file mode 100644 index 00000000..804f894c --- /dev/null +++ b/packages/qwik-web/src/routes/post/[...slug]/index.tsx @@ -0,0 +1,28 @@ +import { component$ } from "@builder.io/qwik"; +import { + routeLoader$, + type StaticGenerateHandler, +} from "@builder.io/qwik-city"; +import { post as posts } from "#site/content"; +import Markdown from "~/components/md"; + +export const usePost = routeLoader$(async (event) => { + return posts.find((post) => post.slug === event.params.slug); +}); + +export default component$(() => { + const post = usePost(); + if (post.value) { + return ; + } else { + return
    ; + } +}); + +export const onStaticSiteGenerate: StaticGenerateHandler = async () => { + return { + params: posts.map((post) => ({ + slug: post.slug, + })), + }; +}; diff --git a/packages/qwik-web/velite.config.js b/packages/qwik-web/velite.config.js index cbfc34e1..15542c4b 100644 --- a/packages/qwik-web/velite.config.js +++ b/packages/qwik-web/velite.config.js @@ -1,22 +1,47 @@ // @ts-check -import { defineConfig,s } from "velite"; +import rehypePrettyCode from "rehype-pretty-code"; +import remarkGemoji from "remark-gemoji"; +import remarkGfm from "remark-gfm"; +import remarkMath from "remark-math"; +import remarkParse from "remark-parse"; +import remarkSectionize from "remark-sectionize"; +import { unified } from "unified"; +import { defineConfig, s } from "velite"; + +const parser = unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkMath) + .use(remarkGemoji) + .use(rehypePrettyCode) + .use(remarkSectionize); export default defineConfig({ collections: { post: { name: "post", pattern: "**/*.md", - schema: s.object({ - title: s.string(), - date: s.string(), - modified: s.string().nullish(), - description: s.string(), - publish: s.boolean(), - tags: s.array(s.string()), - content: s.custom().transform((_, {meta}) => meta.content), - excerpt: s.excerpt() - }) - } - } + schema: s + .object({ + title: s.string(), + date: s.string(), + modified: s.string().nullish(), + description: s.string(), + publish: s.boolean(), + tags: s.array(s.string()), + content: s + .custom() + .transform((_, { meta }) => parser.parse(meta.content)), + excerpt: s.excerpt(), + }) + .transform((data, { meta }) => { + const slug = /\d{4}\/.+\.md$/.exec(meta.path)?.[1]; + return { + ...data, + slug: slug || "", + }; + }), + }, + }, }); diff --git a/packages/remark-sectionize/.gitignore b/packages/remark-sectionize/.gitignore new file mode 100644 index 00000000..9b1ee42e --- /dev/null +++ b/packages/remark-sectionize/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/remark-sectionize/README.md b/packages/remark-sectionize/README.md new file mode 100644 index 00000000..dece96df --- /dev/null +++ b/packages/remark-sectionize/README.md @@ -0,0 +1,15 @@ +# remark-sectionize + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.d.ts +``` + +This project was created using `bun init` in bun v1.1.17. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/packages/remark-sectionize/index.d.ts b/packages/remark-sectionize/index.d.ts new file mode 100644 index 00000000..abe622dc --- /dev/null +++ b/packages/remark-sectionize/index.d.ts @@ -0,0 +1,16 @@ +import { Root, Parent } from "mdast"; + +declare module "remark-sectionize" { + function remarkSectionize(): (root: Root) => void; + export default remarkSectionize; +} + +export interface Section extends Parent { + type: "section"; +} + +declare module "mdast" { + interface RootContentMap { + section: Section; + } +} diff --git a/packages/remark-sectionize/package.json b/packages/remark-sectionize/package.json new file mode 100644 index 00000000..f012f42b --- /dev/null +++ b/packages/remark-sectionize/package.json @@ -0,0 +1,12 @@ +{ + "name": "remark-sectionize", + "module": "index.d.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "@types/mdast": "^4.0.4" + }, + "peerDependencies": { + "typescript": "^5.0.0" + } +} \ No newline at end of file diff --git a/packages/remark-sectionize/tsconfig.json b/packages/remark-sectionize/tsconfig.json new file mode 100644 index 00000000..238655f2 --- /dev/null +++ b/packages/remark-sectionize/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +}