Skip to content

build(docs): use custom Vite plugin to develop docs #55

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package
dist
node_modules
.svelte-kit
.svelte-kit/build
public/build
webpack/public
.astro
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ yarn add svelte-time

The displayed time defaults to `new Date().toISOString()` and is formatted as `"MMM DD, YYYY"`.

<!-- render:Basic -->

```svelte
<script>
import Time from "svelte-time";
Expand All @@ -58,6 +60,8 @@ The displayed time defaults to `new Date().toISOString()` and is formatted as `"

The `timestamp` prop can be any of the following `dayjs` values: `string | number | Date | Dayjs`.

<!-- render:CustomTimestamp -->

```svelte
<Time timestamp="2020-02-01" />

Expand All @@ -68,6 +72,8 @@ The `timestamp` prop can be any of the following `dayjs` values: `string | numbe

Use the `format` prop to format the timestamp. Refer to the [dayjs format documentation](https://day.js.org/docs/en/display/format) for acceptable formats.

<!-- render:CustomFormat -->

```svelte
<Time timestamp="2020-02-01" format="dddd @ h:mm A · MMMM D, YYYY" />

Expand All @@ -80,6 +86,8 @@ Use the `format` prop to format the timestamp. Refer to the [dayjs format docume

Set the `relative` prop value to `true` for the relative time displayed in a human-readable format.

<!-- render:RelativeTime -->

```svelte
<Time relative />

Expand All @@ -92,18 +100,24 @@ When using relative time, the `title` attribute will display a formatted timesta

Use the `format` prop to customize the [format](https://day.js.org/docs/en/display/format).

<!-- render:RelativeTimeCustomFormat -->

```svelte
<Time relative format="dddd @ h:mm A · MMMM D, YYYY" />
```

When using `relative`, the `time` element will set the formatted timestamp as the `title` attribute. Specify a custom `title` to override this.

<!-- render:RelativeTimeCustomTitle -->

```svelte
<Time relative title="Custom title" />
```

Set the value to `undefined` to omit the `title` altogether.

<!-- render:RelativeTimeNoTitle -->

```svelte
<Time relative title={undefined} />
```
Expand Down Expand Up @@ -132,6 +146,8 @@ An alternative to the `Time` component is to use the `svelteTime` action to form

The API is the same as the `Time` component.

<!-- render:SvelteTimeAction -->

```svelte
<script>
import { svelteTime } from "svelte-time";
Expand Down Expand Up @@ -216,6 +232,8 @@ The `dayjs` library is exported from this package for your convenience.

**Note**: the exported `dayjs` function already extends the [relativeTime plugin](https://day.js.org/docs/en/plugin/relative-time).

<!-- render:DayjsExport -->

```svelte
<script>
import { dayjs } from "svelte-time";
Expand All @@ -237,6 +255,8 @@ The default `dayjs` locale is English. No other locale is loaded by default for

To use a [custome locale](https://day.js.org/docs/en/i18n/changing-locale), import the relevant language from `dayjs`. See a list of [supported locales](https://github.com/iamkun/dayjs/tree/dev/src/locale).

<!-- render:CustomLocale -->

```svelte
<script>
import "dayjs/locale/de"; // German locale
Expand Down Expand Up @@ -264,6 +284,8 @@ Use the [`dayjs.locale`](https://day.js.org/docs/en/i18n/changing-locale) method

To use a [custom timezone](https://day.js.org/docs/en/timezone/timezone), import the `utc` and `timezone` plugins from `dayjs`.

<!-- render:CustomTimezone -->

```svelte
<script>
import utc from "dayjs/plugin/utc";
Expand Down
27 changes: 0 additions & 27 deletions astro.config.ts

This file was deleted.

627 changes: 18 additions & 609 deletions bun.lock

Large diffs are not rendered by default.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"type": "module",
"svelte": "./src/index.js",
"scripts": {
"dev": "bun --bun astro dev",
"build": "bun --bun astro build",
"preview": "bun --bun astro preview",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"package": "bun --run dlz",
"format": "bun --bun prettier --write . --cache",
Expand All @@ -19,16 +19,17 @@
"dayjs": "^1.11.13"
},
"devDependencies": {
"@astrojs/mdx": "^4.0.8",
"@astrojs/svelte": "^7.0.4",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@testing-library/svelte": "^5.2.7",
"astro": "^5.3.0",
"dlz": "^0.1.3",
"github-markdown-css": "^5.8.1",
"jsdom": "^26.0.0",
"marked": "^15.0.7",
"marked-base-url": "^1.1.6",
"marked-highlight": "^2.2.1",
"prettier": "^3.5.1",
"prettier-plugin-svelte": "^3.3.3",
"shiki": "^3.0.0",
"svelte": "^5.20.2",
"typescript": "^5.7.3",
"vite": "^6.1.1",
Expand Down
149 changes: 149 additions & 0 deletions plugin-readme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Marked } from "marked";
import { baseUrl } from "marked-base-url";
import { markedHighlight } from "marked-highlight";
import fsp from "node:fs/promises";
import path from "node:path";
import { codeToHtml } from "shiki";
import type { Plugin } from "vite";

type PluginReadmeOptions = {
title: string;
description: string;
watchDir: string;
baseUrl: string;
};

export const pluginReadme = (options: PluginReadmeOptions): Plugin => {
const watchDir = path.join(__dirname, options.watchDir);
let base = "/";

const marked = new Marked(
markedHighlight({
async: true,
async highlight(code, lang) {
const highlightedCode = await codeToHtml(code, {
lang,
theme: "github-dark",
});

return `{@html ${JSON.stringify(highlightedCode)}}\n`;
},
}),
baseUrl(options.baseUrl),
{
renderer: {
html(html) {
if (html.text.includes("render:")) {
// Parse the name from the comment (after "render:")
const regex = /<!--\s*render:(\w+)\s*-->/;
const match = html.text.match(regex);
const componentName = match?.[1];

if (!componentName) {
return html.text;
}

return `<div class="code-demo"><${componentName} /></div>`;
}
return html.text;
},
code(code) {
// Shiki returns a string with the code wrapped in a <pre> tag.
// We need to return the code without the extra <pre> tag.
return code.text;
},
},
gfm: true,
breaks: true,
},
);

return {
name: "vite:process-readme",

// Run before the Svelte plugin.
enforce: "pre",
configureServer(server) {
server.watcher.add(watchDir);
},
handleHotUpdate({ file, server }) {
// If a README.md file changed, force reload the page
// This ensures clean state and no duplicate scripts.
if (file.endsWith("README.md")) {
server.ws.send({ type: "full-reload" });
return [];
}

// If a Svelte component in the watched directory changed,
// let the Svelte plugin handle the HMR.
if (file.startsWith(watchDir) && file.endsWith(".svelte")) {
return;
}
},
configResolved(config) {
// Get the base URL from Vite config.
base = config.base;
},
transformIndexHtml(html) {
return `<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="${options.description}"
/>
<link rel="icon" type="image/svg+xml" href="${base}favicon.svg" />
<title>${options.title}</title>
<style>
html {
color-scheme: dark;
}

main {
max-width: 960px;
margin: auto;
padding: 0 1rem;
}

.markdown-body pre.shiki {
border-radius: 0;
}

.code-demo {
padding: 1rem;
border: 1px solid #24292e;
}
</style>
</head>

<body class="markdown-body">
<main id="readme"></main>
${html}
</body>
</html>
`;
},
async transform(code, id) {
if (id.endsWith("README.md")) {
let imports = "";

const watchedFiles = await fsp.readdir(
path.join(__dirname, options.watchDir),
);

for (const file of watchedFiles) {
if (file.endsWith(".svelte")) {
const moduleName = file.replace(".svelte", "");
imports += `import ${moduleName} from "${options.watchDir}/${file}";\n`;
}
}

const importsBlock = `<script>${imports}</script>`;
const html = await marked.parse(code);

return importsBlock + html;
}
},
};
};
1 change: 1 addition & 0 deletions public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 7 additions & 0 deletions tests/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script type="module">
import "github-markdown-css/github-markdown-dark.css";
import { mount } from "svelte";
import Readme from "../README.md";

mount(Readme, { target: document.getElementById("readme") });
</script>
6 changes: 5 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
"noEmit": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitAny": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "node",
"resolveJsonModule": true,
"strict": true,
"types": ["svelte", "vitest/globals"],
"paths": {
"svelte-time": ["./src"],
"svelte-time/*": ["./src/*"]
}
},
"include": ["src", "tests", "www"]
"include": ["src", "tests", "plugin-readme.ts", "vite.config.ts"]
}
34 changes: 18 additions & 16 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { svelte, vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import { svelte } from "@sveltejs/vite-plugin-svelte";
import path from "node:path";
import { defineConfig } from "vite";
import pkg from "./package.json";
import { pluginReadme } from "./plugin-readme";

export default defineConfig(({ mode }) => ({
export default defineConfig({
base: "/" + pkg.name,
root: "./tests",
build: { outDir: "../dist", emptyOutDir: true },
plugins: [
pluginReadme({
title: pkg.name,
description: pkg.description,
watchDir: "./tests/examples",
baseUrl: "https://github.com/metonym/svelte-time/tree/master/",
}),
svelte({
compilerOptions: {
runes: true,
},
hot: false,
preprocess: [vitePreprocess()],
compilerOptions: { runes: true },
extensions: [".svelte", ".md"],
}),
],
resolve: {
alias: {
[pkg.name]: path.resolve("./src"),
},
conditions: mode === "test" ? ["browser"] : [],
},
test: {
globals: true,
environment: "jsdom",
alias: { [pkg.name]: path.resolve("./src") },
conditions: ["browser"],
},
}));
test: { globals: true, environment: "jsdom" },
});
Loading