rangefind-astro 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.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # rangefind-astro
2
+
3
+ An [Astro](https://astro.build) integration for
4
+ [Rangefind](https://github.com/xjodoin/rangefind) — build a static search index
5
+ from your site at `astro build` time and drop an accessible search box into any
6
+ page. No search server: the index is static files served with HTTP `Range`
7
+ requests.
8
+
9
+ ## How it works
10
+
11
+ On the `astro:build:done` hook the integration:
12
+
13
+ 1. Crawls your built static output with Rangefind's crawler, indexing every
14
+ `.html` page's title, headings, and body.
15
+ 2. Writes the index into your build output (default `dist/rangefind/`) so it
16
+ deploys alongside the rest of your site.
17
+ 3. Copies the drop-in `<rangefind-search>` web component and its optional theme
18
+ into `dist/_rangefind/` for the `RangefindSearch` component to load.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ npm install rangefind-astro
24
+ ```
25
+
26
+ `astro` and `rangefind` are the only requirements — no framework (React/Vue/…)
27
+ is needed; the search UI is a framework-free web component.
28
+
29
+ ## Add the integration
30
+
31
+ ```js
32
+ // astro.config.mjs
33
+ import { defineConfig } from "astro/config";
34
+ import rangefind from "rangefind-astro";
35
+
36
+ export default defineConfig({
37
+ integrations: [rangefind()]
38
+ });
39
+ ```
40
+
41
+ ## Add the search box
42
+
43
+ Drop the component into any `.astro` page or layout:
44
+
45
+ ```astro
46
+ ---
47
+ import RangefindSearch from "rangefind-astro/RangefindSearch.astro";
48
+ ---
49
+ <RangefindSearch placeholder="Search the docs…" />
50
+ ```
51
+
52
+ That renders the `<rangefind-search>` element and a module script that loads it
53
+ from the copied assets. It is **headless by default** — no styling of its own —
54
+ so your CSS (Tailwind utilities, global stylesheets) applies directly. Pass
55
+ `theme` to also link Rangefind's optional stylesheet.
56
+
57
+ ## Integration options
58
+
59
+ `rangefind(options)`:
60
+
61
+ | Option | Default | Purpose |
62
+ | --- | --- | --- |
63
+ | `enabled` | `true` | Set `false` to skip index building (per-environment opt-out). |
64
+ | `baseUrl` | `"/"` | URL prefix/origin for result URLs. A path like `/docs/` prefixes every URL; an absolute origin like `https://example.com/` produces absolute URLs. Match your Astro `base`/`site` if you deploy under a subpath. |
65
+ | `outputDir` | `"rangefind"` | Where the index is written, relative to the build output (or an absolute path). Nested under the site output by default so it deploys with everything else. |
66
+ | `assetsDir` | `"_rangefind"` | Where the client component assets are copied, relative to the build output (or absolute). |
67
+
68
+ If you change `outputDir` / `assetsDir`, point the component's `src` /
69
+ `assetsBase` props at the matching URLs.
70
+
71
+ ## `<RangefindSearch />` props
72
+
73
+ | Prop | Default | Purpose |
74
+ | --- | --- | --- |
75
+ | `src` | `"/rangefind/"` | Index base URL (passed to `createSearch({ baseUrl })`). Set to match `outputDir` / your deploy base. |
76
+ | `assetsBase` | `"/_rangefind/"` | URL directory the component `.js`/`.css` were copied to. |
77
+ | `theme` | `false` | When truthy, link Rangefind's optional theme stylesheet. Leave off to style it yourself. |
78
+ | `placeholder` | `Search` | Input placeholder. |
79
+
80
+ Every other attribute of the underlying `<rangefind-search>` web component
81
+ passes straight through. Config attributes: `label`, `page-size`, `debounce`,
82
+ `min-length`, `highlight`, `suggest`, `router`, `open-on-focus`, `hotkey`,
83
+ `empty-text`, `loading-text`, `error-text`. Per-part class hooks (anything
84
+ matching `*-class`, e.g. `input-class`, `option-class`, `mark-class`) also pass
85
+ through — ideal for Tailwind:
86
+
87
+ ```astro
88
+ <RangefindSearch
89
+ placeholder="Search…"
90
+ hotkey
91
+ input-class="w-full border rounded px-3 py-2"
92
+ mark-class="bg-yellow-200"
93
+ />
94
+ ```
95
+
96
+ Boolean props map faithfully: `highlight={false}` renders `highlight="false"`
97
+ (disabling it), while an unset prop keeps the component's own default.
98
+
99
+ For the full attribute, class-hook, event, and accessibility reference, see the
100
+ Rangefind docs' **Search UI component** section:
101
+ [`docs/reference.md`](https://github.com/xjodoin/rangefind/blob/main/docs/reference.md).
102
+
103
+ ## Notes
104
+
105
+ - The `astro:build:done` hook only runs on `astro build`, so `astro dev` is
106
+ unaffected (there is no index during dev).
107
+ - The build fails loudly if Rangefind finds no indexable HTML — the integration
108
+ was added on purpose, so an empty index is treated as an error.
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "rangefind-astro",
3
+ "version": "0.3.0",
4
+ "description": "Astro integration for Rangefind — builds a static search index at `astro build` time and ships a drop-in search component.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Xavier Jodoin <xavier@jodoin.me>",
8
+ "keywords": [
9
+ "astro",
10
+ "astro-integration",
11
+ "astro-component",
12
+ "search",
13
+ "static-search",
14
+ "rangefind"
15
+ ],
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
19
+ "exports": {
20
+ ".": "./src/index.js",
21
+ "./RangefindSearch.astro": "./src/RangefindSearch.astro",
22
+ "./package.json": "./package.json"
23
+ },
24
+ "files": [
25
+ "src/",
26
+ "README.md"
27
+ ],
28
+ "scripts": {
29
+ "test": "node --test test/build.test.js"
30
+ },
31
+ "dependencies": {
32
+ "rangefind": "^0.3.0"
33
+ },
34
+ "peerDependencies": {
35
+ "astro": ">=4.0.0"
36
+ },
37
+ "peerDependenciesMeta": {
38
+ "astro": {
39
+ "optional": true
40
+ }
41
+ },
42
+ "devDependencies": {
43
+ "astro": "^5.13.0"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/xjodoin/rangefind.git",
48
+ "directory": "packages/rangefind-astro"
49
+ },
50
+ "homepage": "https://github.com/xjodoin/rangefind/tree/main/packages/rangefind-astro#readme",
51
+ "bugs": {
52
+ "url": "https://github.com/xjodoin/rangefind/issues"
53
+ }
54
+ }
@@ -0,0 +1,45 @@
1
+ ---
2
+ // Thin, faithful passthrough to the `<rangefind-search>` web component. All
3
+ // behavior lives in the component itself; this only wires up the client asset
4
+ // URLs and maps props onto the element's real attributes (see the monorepo's
5
+ // docs/reference.md "Search UI component" section for every attribute + class
6
+ // hook). Headless by default — the optional theme stylesheet is linked only
7
+ // when `theme` is truthy.
8
+
9
+ // The web component's real config attributes (from src/rangefind_search_element.js
10
+ // CONFIG_ATTRS). `src` is handled explicitly below.
11
+ const CONFIG_ATTRS = [
12
+ "placeholder", "page-size", "debounce", "min-length", "highlight",
13
+ "suggest", "router", "open-on-focus", "hotkey", "label", "empty-text",
14
+ "loading-text", "error-text"
15
+ ];
16
+
17
+ const {
18
+ src = "/rangefind/",
19
+ assetsBase = "/_rangefind/",
20
+ theme = false,
21
+ placeholder,
22
+ ...rest
23
+ } = Astro.props;
24
+
25
+ // Build the attribute map for the custom element. Only known config attributes
26
+ // and per-part `*-class` attributes pass through — nothing is invented.
27
+ const attrs = { src };
28
+ if (placeholder !== undefined && placeholder !== null) attrs.placeholder = placeholder;
29
+ for (const [key, value] of Object.entries(rest)) {
30
+ if (value === undefined || value === null) continue;
31
+ if (!CONFIG_ATTRS.includes(key) && !/-class$/.test(key)) continue;
32
+ // Faithful boolean mapping: `true` -> bare attribute, `false` -> "false"
33
+ // (so e.g. highlight={false} actually disables it), else the string value.
34
+ if (value === true) attrs[key] = "";
35
+ else if (value === false) attrs[key] = "false";
36
+ else attrs[key] = String(value);
37
+ }
38
+
39
+ const base = String(assetsBase).endsWith("/") ? assetsBase : `${assetsBase}/`;
40
+ const scriptSrc = `${base}rangefind-search.js`;
41
+ const styleHref = `${base}rangefind-search.css`;
42
+ ---
43
+ {theme && <link rel="stylesheet" href={styleHref} />}
44
+ <script is:inline type="module" src={scriptSrc}></script>
45
+ <rangefind-search {...attrs}></rangefind-search>
package/src/index.js ADDED
@@ -0,0 +1,96 @@
1
+ // Astro integration for Rangefind.
2
+ //
3
+ // At `astro build` time (the `astro:build:done` hook) this crawls the built
4
+ // static output with Rangefind's own crawler, writes the search index nested
5
+ // under the site output so it deploys with everything else, and copies the
6
+ // drop-in `<rangefind-search>` web component + optional theme into a public
7
+ // assets directory. Pair it with the `RangefindSearch.astro` component.
8
+
9
+ import { mkdir, copyFile } from "node:fs/promises";
10
+ import { fileURLToPath } from "node:url";
11
+ import { join, isAbsolute } from "node:path";
12
+ import { buildFromCrawl } from "rangefind/crawler";
13
+
14
+ const NAME = "rangefind";
15
+
16
+ // Resolve a client asset shipped by the `rangefind` package to a real file
17
+ // path. `import.meta.resolve` returns a `file://` URL for a package export.
18
+ function resolveAsset(specifier) {
19
+ return fileURLToPath(import.meta.resolve(specifier));
20
+ }
21
+
22
+ // Join a configured sub-path onto the build output directory. Absolute paths
23
+ // are honored as-is; relative paths (the default) stay nested under the site.
24
+ function resolveUnderOut(outDir, subPath) {
25
+ return isAbsolute(subPath) ? subPath : join(outDir, subPath);
26
+ }
27
+
28
+ /**
29
+ * @param {object} [options]
30
+ * @param {boolean} [options.enabled=true] Set false to skip indexing entirely.
31
+ * @param {string} [options.baseUrl="/"] URL prefix/origin for result URLs.
32
+ * @param {string} [options.outputDir="rangefind"] Index output dir, relative to the build output (or absolute).
33
+ * @param {string} [options.assetsDir="_rangefind"] Client-asset dir, relative to the build output (or absolute).
34
+ * @returns {import("astro").AstroIntegration}
35
+ */
36
+ export default function rangefindAstro(options = {}) {
37
+ const {
38
+ enabled = true,
39
+ baseUrl = "/",
40
+ outputDir = "rangefind",
41
+ assetsDir = "_rangefind"
42
+ } = options;
43
+
44
+ return {
45
+ name: NAME,
46
+ hooks: {
47
+ "astro:build:done": async ({ dir, logger }) => {
48
+ // `astro:build:done` only fires for `astro build`, so there is no dev
49
+ // mode to guard against — the `enabled` flag is the per-env opt-out.
50
+ if (enabled === false) {
51
+ logger.info("disabled (options.enabled === false); skipping index build");
52
+ return;
53
+ }
54
+ // `dir` may be missing on server/adapter builds that emit no static
55
+ // HTML output directory; skip cleanly rather than crash.
56
+ if (!dir) {
57
+ logger.warn("no static output directory for this build; skipping index build");
58
+ return;
59
+ }
60
+
61
+ const outDir = fileURLToPath(dir);
62
+ const indexOut = resolveUnderOut(outDir, outputDir);
63
+ const assetsOut = resolveUnderOut(outDir, assetsDir);
64
+
65
+ try {
66
+ const result = await buildFromCrawl({
67
+ root: outDir,
68
+ scanDir: outDir,
69
+ output: indexOut,
70
+ baseUrl
71
+ });
72
+ logger.info(
73
+ `indexed ${result.docs} doc(s) from ${result.files} file(s) -> ${indexOut}`
74
+ );
75
+ } catch (error) {
76
+ // A site with no indexable HTML (e.g. crawler throws) should fail the
77
+ // build loudly — the developer added this integration on purpose.
78
+ logger.error(`index build failed: ${error?.message || error}`);
79
+ throw error;
80
+ }
81
+
82
+ // Copy the client assets so `<rangefind-search>` can load them.
83
+ await mkdir(assetsOut, { recursive: true });
84
+ await copyFile(
85
+ resolveAsset("rangefind/element"),
86
+ join(assetsOut, "rangefind-search.js")
87
+ );
88
+ await copyFile(
89
+ resolveAsset("rangefind/element.css"),
90
+ join(assetsOut, "rangefind-search.css")
91
+ );
92
+ logger.info(`copied search component assets -> ${assetsOut}`);
93
+ }
94
+ }
95
+ };
96
+ }