docusaurus-plugin-rangefind 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +127 -0
  2. package/index.js +113 -0
  3. package/package.json +44 -0
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # docusaurus-plugin-rangefind
2
+
3
+ Add fast, fully static search to a [Docusaurus](https://docusaurus.io) site with
4
+ [Rangefind](https://github.com/xjodoin/rangefind) — a search engine that ships a
5
+ **static index** and answers queries in the browser with **HTTP Range
6
+ requests**. No search server, no third-party service, no crawl-time API keys.
7
+
8
+ At `docusaurus build` time the plugin:
9
+
10
+ 1. Crawls the freshly built HTML in `outDir` and writes a Rangefind index to
11
+ `build/rangefind/` (via `buildFromCrawl` from `rangefind/crawler`).
12
+ 2. Copies the drop-in search web component and its optional theme into
13
+ `build/_rangefind/`.
14
+ 3. Injects the component's `<script type="module">` (and, by default, the theme
15
+ `<link rel="stylesheet">`) on **every page**.
16
+
17
+ You then place the `<rangefind-search>` box wherever you want it.
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install --save-dev docusaurus-plugin-rangefind rangefind
23
+ ```
24
+
25
+ `rangefind` is a runtime peer used at build time (crawler + client bundle);
26
+ `@docusaurus/core` >= 3 is a peer.
27
+
28
+ ## Configure
29
+
30
+ `docusaurus.config.js`:
31
+
32
+ ```js
33
+ module.exports = {
34
+ // ...
35
+ plugins: ["docusaurus-plugin-rangefind"]
36
+ };
37
+ ```
38
+
39
+ Or with options:
40
+
41
+ ```js
42
+ module.exports = {
43
+ plugins: [
44
+ ["docusaurus-plugin-rangefind", { theme: true }]
45
+ ]
46
+ };
47
+ ```
48
+
49
+ ## Place the search box
50
+
51
+ The plugin injects the component's script/style globally but **does not** render
52
+ a search box for you — that would force a duplicate box onto every page with no
53
+ control over placement (this mirrors how other Docusaurus search plugins work).
54
+ Instead, `<rangefind-search>` is a plain custom element, so you drop it in
55
+ wherever you like. Point its `src` at the index, which lives at
56
+ `<baseUrl>rangefind/` (so `/rangefind/` for a root deploy, `/my-repo/rangefind/`
57
+ under a sub-path `baseUrl`).
58
+
59
+ **Option A — a navbar item (recommended).** Use Docusaurus's built-in `html`
60
+ navbar item type:
61
+
62
+ ```js
63
+ themeConfig: {
64
+ navbar: {
65
+ items: [
66
+ {
67
+ type: "html",
68
+ position: "right",
69
+ value: '<rangefind-search src="/rangefind/" placeholder="Search…"></rangefind-search>'
70
+ }
71
+ ]
72
+ }
73
+ }
74
+ ```
75
+
76
+ **Option B — directly in any Markdown/MDX page.** Because it is a custom
77
+ element, no React wrapper is needed once the script is injected:
78
+
79
+ ```mdx
80
+ <rangefind-search src="/rangefind/" placeholder="Search the docs…"></rangefind-search>
81
+ ```
82
+
83
+ **Option C — a swizzled component.** Swizzle e.g. `Navbar/Content` and render
84
+ `<rangefind-search src="/rangefind/" />` in your JSX. React passes custom
85
+ elements through untouched.
86
+
87
+ ## Options
88
+
89
+ All options are optional.
90
+
91
+ | Option | Type | Default | Effect |
92
+ | --- | --- | --- | --- |
93
+ | `enabled` | `boolean` | `true` | Set `false` to skip indexing and injection entirely (e.g. in a preview build). |
94
+ | `theme` | `boolean` | `true` | Inject the optional `rangefind-search.css` theme link. Set `false` if you style the component yourself. |
95
+ | `baseUrl` | `string` | site `baseUrl` | URL prefix for result URLs and the injected asset tags. Defaults to Docusaurus's own `baseUrl`, which is correct for sub-path deploys. |
96
+ | `outputDir` | `string` | `"rangefind"` | Index directory, relative to `outDir` (absolute paths are used as-is). This is also where your `<rangefind-search src>` should point (`<baseUrl><outputDir>/`). |
97
+ | `assetsDir` | `string` | `"_rangefind"` | Directory (relative to `outDir`) for the copied `rangefind-search.js` / `.css`. |
98
+
99
+ ## Styling
100
+
101
+ Zero framework is required beyond Docusaurus. The `<rangefind-search>` element
102
+ renders into its **light DOM** with no styling of its own, so:
103
+
104
+ - The bundled theme (`theme: true`, on by default) gives you a usable look with
105
+ light/dark palettes out of the box.
106
+ - With Tailwind or any CSS framework, disable the theme (`theme: false`) and use
107
+ the component's per-part `*-class` attributes (`input-class`, `option-class`,
108
+ `mark-class`, …) or namespaced `rf-search__*` hook classes.
109
+
110
+ See the Rangefind monorepo's component reference (the "Search UI component"
111
+ section of `docs/reference.md`) for the full attribute list, class hooks,
112
+ events, and accessibility details — this plugin does not change that API, it
113
+ only wires the component into the Docusaurus build.
114
+
115
+ ## How it works
116
+
117
+ - **Hooks:** the plugin uses the official `postBuild(props)` lifecycle hook
118
+ (crawl + asset copy, keyed off `props.outDir`) and `injectHtmlTags()`
119
+ (global script + optional style in `postBodyTags`).
120
+ - **Asset resolution:** the client bundle and theme are located with
121
+ `require.resolve("rangefind/element")` and
122
+ `require.resolve("rangefind/element.css")`, so they always match the installed
123
+ `rangefind` version; the crawler is loaded via a dynamic
124
+ `import("rangefind/crawler")`.
125
+ - **Deployment:** Rangefind needs a static host that supports HTTP `Range`
126
+ requests (GitHub Pages, Netlify, S3/CloudFront, nginx, …). See the Rangefind
127
+ deployment notes for MIME/caching guidance.
package/index.js ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+
3
+ // docusaurus-plugin-rangefind — index a built Docusaurus site with Rangefind
4
+ // (a static search engine: static index + HTTP Range requests, no server) and
5
+ // inject the drop-in `<rangefind-search>` web component site-wide.
6
+ //
7
+ // This module is CommonJS on purpose: Docusaurus loads plugin modules with a
8
+ // CJS-first loader, and `require.resolve` gives us the real on-disk paths of
9
+ // the `rangefind/element` bundle and its CSS without executing them. The
10
+ // crawler (`rangefind/crawler`) is ESM in an otherwise-ESM package, so it is
11
+ // pulled in with a dynamic `import()` from inside the async `postBuild` hook —
12
+ // that works from CJS regardless of the target module's format.
13
+
14
+ const fs = require("node:fs");
15
+ const path = require("node:path");
16
+
17
+ const DEFAULT_OUTPUT_DIR = "rangefind"; // index dir, relative to outDir
18
+ const DEFAULT_ASSETS_DIR = "_rangefind"; // client asset dir, relative to outDir
19
+ const ASSET_JS = "rangefind-search.js";
20
+ const ASSET_CSS = "rangefind-search.css";
21
+
22
+ // Docusaurus base URLs always carry a trailing slash; normalize so callers can
23
+ // pass "/base" or "/base/" interchangeably and our string joins never double
24
+ // or drop the separator.
25
+ function normalizeBaseUrl(baseUrl) {
26
+ const value = String(baseUrl || "/");
27
+ return value.endsWith("/") ? value : `${value}/`;
28
+ }
29
+
30
+ module.exports = function rangefindPlugin(context = {}, options = {}) {
31
+ const enabled = options.enabled !== false;
32
+ const theme = options.theme !== false;
33
+ const assetsDir = options.assetsDir || DEFAULT_ASSETS_DIR;
34
+ const outputDir = options.outputDir || DEFAULT_OUTPUT_DIR;
35
+
36
+ // Base URL for the injected asset tags. Prefer an explicit override, then the
37
+ // site's own configured baseUrl so the tag src matches the deployed prefix.
38
+ const siteBaseUrl = normalizeBaseUrl(
39
+ options.baseUrl != null ? options.baseUrl : context && context.siteConfig && context.siteConfig.baseUrl
40
+ );
41
+
42
+ return {
43
+ name: "docusaurus-plugin-rangefind",
44
+
45
+ // Official post-build lifecycle hook: the whole site is already emitted to
46
+ // props.outDir, so we crawl that HTML into a Rangefind index and copy the
47
+ // search UI assets alongside it.
48
+ async postBuild(props) {
49
+ if (!enabled) return;
50
+
51
+ const outDir = props.outDir;
52
+ // buildFromCrawl's baseUrl is the URL prefix stamped onto every result
53
+ // URL; Docusaurus's own baseUrl is the right default so links resolve on
54
+ // the deployed site (root-relative under any sub-path deploy).
55
+ const crawlBaseUrl = normalizeBaseUrl(
56
+ options.baseUrl != null
57
+ ? options.baseUrl
58
+ : props.baseUrl != null
59
+ ? props.baseUrl
60
+ : props.siteConfig && props.siteConfig.baseUrl != null
61
+ ? props.siteConfig.baseUrl
62
+ : siteBaseUrl
63
+ );
64
+
65
+ const output = path.isAbsolute(outputDir) ? outputDir : path.join(outDir, outputDir);
66
+
67
+ // ESM crawler pulled in dynamically from this CJS module.
68
+ const { buildFromCrawl } = await import("rangefind/crawler");
69
+ await buildFromCrawl({
70
+ root: outDir,
71
+ scanDir: outDir,
72
+ output,
73
+ baseUrl: crawlBaseUrl
74
+ });
75
+
76
+ // Copy the two client assets next to the site. Paths resolve through the
77
+ // `rangefind` package exports map to the real dist files.
78
+ const assetsOut = path.join(outDir, assetsDir);
79
+ await fs.promises.mkdir(assetsOut, { recursive: true });
80
+ const jsSrc = require.resolve("rangefind/element");
81
+ const cssSrc = require.resolve("rangefind/element.css");
82
+ await fs.promises.copyFile(jsSrc, path.join(assetsOut, ASSET_JS));
83
+ await fs.promises.copyFile(cssSrc, path.join(assetsOut, ASSET_CSS));
84
+ },
85
+
86
+ // Inject the search element's script (and, unless disabled, the optional
87
+ // theme) on every page. The `<rangefind-search>` element itself is NOT
88
+ // injected here — users place it where they want (navbar html item, MDX,
89
+ // or a swizzled component); see the README.
90
+ injectHtmlTags() {
91
+ if (!enabled) return {};
92
+ const tags = [
93
+ {
94
+ tagName: "script",
95
+ attributes: {
96
+ type: "module",
97
+ src: `${siteBaseUrl}${assetsDir}/${ASSET_JS}`
98
+ }
99
+ }
100
+ ];
101
+ if (theme) {
102
+ tags.push({
103
+ tagName: "link",
104
+ attributes: {
105
+ rel: "stylesheet",
106
+ href: `${siteBaseUrl}${assetsDir}/${ASSET_CSS}`
107
+ }
108
+ });
109
+ }
110
+ return { postBodyTags: tags };
111
+ }
112
+ };
113
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "docusaurus-plugin-rangefind",
3
+ "version": "0.3.0",
4
+ "description": "Docusaurus plugin that indexes your built site with Rangefind (static search over HTTP Range requests, no server) and injects the drop-in <rangefind-search> web component.",
5
+ "license": "MIT",
6
+ "author": "Xavier Jodoin <xavier@jodoin.me>",
7
+ "keywords": [
8
+ "docusaurus",
9
+ "docusaurus-plugin",
10
+ "search",
11
+ "rangefind",
12
+ "static-search",
13
+ "client-side-search"
14
+ ],
15
+ "main": "index.js",
16
+ "files": [
17
+ "index.js",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "test": "node --test test/*.test.js"
22
+ },
23
+ "peerDependencies": {
24
+ "@docusaurus/core": ">=3.0.0"
25
+ },
26
+ "dependencies": {
27
+ "rangefind": "^0.3.0"
28
+ },
29
+ "devDependencies": {
30
+ "@docusaurus/core": "^3",
31
+ "@docusaurus/preset-classic": "^3",
32
+ "react": "^18.3.1",
33
+ "react-dom": "^18.3.1"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/xjodoin/rangefind.git",
38
+ "directory": "packages/docusaurus-plugin-rangefind"
39
+ },
40
+ "homepage": "https://github.com/xjodoin/rangefind/tree/main/packages/docusaurus-plugin-rangefind#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/xjodoin/rangefind/issues"
43
+ }
44
+ }