Skip to content

Commit fc07c91

Browse files
committed
chore: replace svelte-readme with custom Vite app
1 parent e9aa322 commit fc07c91

File tree

11 files changed

+306
-124
lines changed

11 files changed

+306
-124
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
.DS_Store
22
dist
3-
node_modules
3+
node_modules

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Bind to the `state` prop to determine if the current tab is currently visible or
4747

4848
In the following example, switch to a different tab in the same browser window. The page title should change from "visible" to "hidden."
4949

50+
<!-- render:Basic -->
51+
5052
```svelte
5153
<script>
5254
import VisibilityChange from "svelte-visibility-change";
@@ -65,7 +67,7 @@ In the following example, switch to a different tab in the same browser window.
6567

6668
You can also bind to the boolean `visible` and `hidden` props.
6769

68-
```svelte no-eval
70+
```svelte
6971
<script>
7072
import VisibilityChange from "svelte-visibility-change";
7173
@@ -80,6 +82,8 @@ You can also bind to the boolean `visible` and `hidden` props.
8082

8183
An alternative to binding to props is to listen to the `on:visible` and `on:hidden` dispatched events.
8284

85+
<!-- render:Events -->
86+
8387
```svelte
8488
<script>
8589
import VisibilityChange from "svelte-visibility-change";
@@ -101,7 +105,7 @@ This component dispatches an `on:change` event whenever a [visibilitychange](htt
101105

102106
**Note:** unlike `on:visible`, this event only fires when the page visibility changes _after the component has mounted._
103107

104-
```svelte no-eval
108+
```svelte
105109
<VisibilityChange
106110
on:change={({ detail }) => {
107111
console.log(detail.state); // "visible" | "hidden"
@@ -117,7 +121,7 @@ An alternative to the `VisibilityChange` component is the `visibilityChange` act
117121

118122
The action only dispatches a "change" event with the same `event.detail` signature.
119123

120-
```svelte no-eval
124+
```svelte
121125
<script>
122126
import { visibilityChange } from "svelte-visibility-change";
123127
</script>
@@ -129,7 +133,8 @@ The action only dispatches a "change" event with the same `event.detail` signatu
129133
console.log(detail.visible); // boolean
130134
console.log(detail.hidden); // boolean
131135
}}
132-
/>
136+
>
137+
</div>
133138
```
134139

135140
## API

bun.lock

Lines changed: 85 additions & 90 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,23 @@
2323
}
2424
},
2525
"scripts": {
26-
"dev": "rollup -cw",
27-
"build": "rollup -c",
26+
"dev": "vite",
27+
"build": "vite build",
28+
"preview": "vite preview",
2829
"test": "vitest"
2930
},
3031
"devDependencies": {
3132
"@sveltejs/vite-plugin-svelte": "^5.0.3",
3233
"@testing-library/svelte": "^5.2.7",
34+
"github-markdown-css": "^5.8.1",
3335
"jsdom": "^26.0.0",
36+
"marked": "^15.0.7",
37+
"marked-base-url": "^1.1.6",
38+
"marked-highlight": "^2.2.1",
39+
"shiki": "^3.2.1",
3440
"svelte": "^5.25.2",
35-
"svelte-readme": "^3.6.3",
3641
"typescript": "^5.8.2",
42+
"vite": "^6.2.2",
3743
"vitest": "^3.0.9"
3844
},
3945
"repository": {

plugin-readme.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { Marked } from "marked";
2+
import { baseUrl } from "marked-base-url";
3+
import { markedHighlight } from "marked-highlight";
4+
import fsp from "node:fs/promises";
5+
import path from "node:path";
6+
import { codeToHtml } from "shiki";
7+
import type { Plugin } from "vite";
8+
9+
type PluginReadmeOptions = {
10+
title: string;
11+
description: string;
12+
watchDir: string;
13+
baseUrl: string;
14+
};
15+
16+
export const pluginReadme = (options: PluginReadmeOptions): Plugin => {
17+
const watchDir = path.join(__dirname, options.watchDir);
18+
let base = "/";
19+
20+
const marked = new Marked(
21+
markedHighlight({
22+
async: true,
23+
async highlight(code, lang) {
24+
const highlightedCode = await codeToHtml(code, {
25+
lang,
26+
theme: "github-dark",
27+
});
28+
29+
return `{@html ${JSON.stringify(highlightedCode)}}\n`;
30+
},
31+
}),
32+
baseUrl(options.baseUrl),
33+
{
34+
renderer: {
35+
html(html) {
36+
if (html.text.includes("render:")) {
37+
// Parse the name from the comment (after "render:")
38+
const regex = /<!--\s*render:(\w+)\s*-->/;
39+
const match = html.text.match(regex);
40+
const componentName = match?.[1];
41+
42+
if (!componentName) {
43+
return html.text;
44+
}
45+
46+
return `<div class="code-demo"><${componentName} /></div>`;
47+
}
48+
return html.text;
49+
},
50+
code(code) {
51+
// Shiki returns a string with the code wrapped in a <pre> tag.
52+
// We need to return the code without the extra <pre> tag.
53+
return code.text;
54+
},
55+
},
56+
gfm: true,
57+
breaks: true,
58+
},
59+
);
60+
61+
return {
62+
name: "vite:process-readme",
63+
64+
// Run before the Svelte plugin.
65+
enforce: "pre",
66+
configureServer(server) {
67+
server.watcher.add(watchDir);
68+
},
69+
handleHotUpdate({ file, server }) {
70+
// If a README.md file changed, force reload the page
71+
// This ensures clean state and no duplicate scripts.
72+
if (file.endsWith("README.md")) {
73+
server.ws.send({ type: "full-reload" });
74+
return [];
75+
}
76+
77+
// If a Svelte component in the watched directory changed,
78+
// let the Svelte plugin handle the HMR.
79+
if (file.startsWith(watchDir) && file.endsWith(".svelte")) {
80+
return;
81+
}
82+
},
83+
configResolved(config) {
84+
// Get the base URL from Vite config.
85+
base = config.base;
86+
},
87+
transformIndexHtml(html) {
88+
return `<html lang="en">
89+
<head>
90+
<meta charset="utf-8" />
91+
<meta name="viewport" content="width=device-width, initial-scale=1" />
92+
<meta
93+
name="description"
94+
content="${options.description}"
95+
/>
96+
<link rel="icon" type="image/svg+xml" href="${base}favicon.svg" />
97+
<title>${options.title}</title>
98+
<style>
99+
html {
100+
color-scheme: dark;
101+
}
102+
103+
main {
104+
max-width: 960px;
105+
margin: auto;
106+
padding: 0 1rem;
107+
}
108+
109+
.markdown-body pre.shiki {
110+
border-radius: 0;
111+
}
112+
113+
.code-demo {
114+
padding: 1rem;
115+
border: 1px solid #24292e;
116+
}
117+
</style>
118+
</head>
119+
120+
<body class="markdown-body">
121+
<main id="readme"></main>
122+
${html}
123+
</body>
124+
</html>
125+
`;
126+
},
127+
async transform(code, id) {
128+
if (id.endsWith("README.md")) {
129+
let imports = "";
130+
131+
const watchedFiles = await fsp.readdir(
132+
path.join(__dirname, options.watchDir),
133+
);
134+
135+
for (const file of watchedFiles) {
136+
if (file.endsWith(".svelte")) {
137+
const moduleName = file.replace(".svelte", "");
138+
imports += `import ${moduleName} from "${options.watchDir}/${file}";\n`;
139+
}
140+
}
141+
142+
const importsBlock = `<script>${imports}</script>`;
143+
const html = await marked.parse(code);
144+
145+
return importsBlock + html;
146+
}
147+
},
148+
};
149+
};

rollup.config.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

tests/examples/Basic.svelte

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script lang="ts">
2+
import VisibilityChange from "svelte-visibility-change";
3+
4+
let state: "visible" | "hidden";
5+
6+
$: if (typeof window !== "undefined") {
7+
document.title = state;
8+
}
9+
</script>
10+
11+
<VisibilityChange bind:state />

tests/examples/Events.svelte

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script lang="ts">
2+
import VisibilityChange from "svelte-visibility-change";
3+
4+
let events: string[] = [];
5+
</script>
6+
7+
<VisibilityChange
8+
on:visible={() => (events = [...events, "on:visible"])}
9+
on:hidden={() => (events = [...events, "on:hidden"])}
10+
/>
11+
12+
{events.join(", ")}

tests/index.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script type="module">
2+
import "github-markdown-css/github-markdown-dark.css";
3+
import { mount } from "svelte";
4+
import Readme from "../README.md";
5+
6+
mount(Readme, { target: document.getElementById("readme") });
7+
</script>

tsconfig.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
"compilerOptions": {
33
"noEmit": true,
44
"checkJs": true,
5+
"esModuleInterop": true,
6+
"forceConsistentCasingInFileNames": true,
57
"noUnusedLocals": true,
68
"noUnusedParameters": true,
79
"noImplicitAny": true,
810
"verbatimModuleSyntax": true,
911
"isolatedModules": true,
1012
"target": "ESNext",
1113
"module": "ESNext",
12-
"moduleResolution": "bundler",
14+
"moduleResolution": "node",
1315
"resolveJsonModule": true,
1416
"strict": true,
1517
"types": ["svelte", "vitest/globals"],
@@ -18,5 +20,5 @@
1820
"svelte-visibility-change/*": ["./src/*"]
1921
}
2022
},
21-
"include": ["src", "tests"]
23+
"include": ["src", "tests", "plugin-readme.ts", "vite.config.ts"]
2224
}

0 commit comments

Comments
 (0)