Skip to content

Commit 0ce505a

Browse files
authored
[search] add search keyword modifiers (#1397)
Add a method to attach arbitrary keywords or search terms to specific URLs in search index. See changes to CONTRIBUTING.md for authoring details
1 parent 7c19cc2 commit 0ce505a

File tree

10 files changed

+230
-4
lines changed

10 files changed

+230
-4
lines changed

CONTRIBUTING.md

+24
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,30 @@ Sometimes we want to include something that is not on lit.dev in the search inde
8787
}
8888
```
8989

90+
### How to associate keywords to a search item
91+
92+
Sometimes the text of a search item does not include certain keywords you'd like to
93+
associate with that URL. e.g. If someone searches for a react term like `componentDidMount`, or `onMounted` in Vue,
94+
the text for `connectedCallback` did not include `componentDidMount` or `onMounted` in the text. To associate that term with `connectedCallback` you would modify the `packages/lit-dev-content/site/_data/keywords.json` file to include associate keywords with a set of URLs like so:
95+
96+
```json
97+
{
98+
"keywords": [
99+
{
100+
"keywords": ["componentDidMount", "onMounted"],
101+
"urls": [
102+
"/docs/components/lifecycle/#connectedcallback",
103+
"/articles/lit-cheat-sheet/#connectedcallback"
104+
]
105+
}
106+
]
107+
}
108+
```
109+
110+
This will add some more context for algolia to search on when a user searches
111+
for `componentDidMount` or `onMounted`. The keywords will not be rendered in the
112+
UI, but the results should be.
113+
90114
### How to administer the Algolia search index
91115

92116
To administer the search index to add, remove, delete, enable Algolia features, etc., you must be a part of the Algolia team which has limited space and is currently limited to the Lit team. Contact Elliott on the Lit team if you need access. We do not use Algolia Analytics as we have not had the time to go through Google's privacy review + privacy policy / cookie process for storing user data in Algolia.

packages/lit-dev-content/site/_data/externalSearchData.json

+22
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,27 @@
5353
"tag": "other"
5454
},
5555
"isExternal": true
56+
},
57+
{
58+
"relativeUrl": "https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/exportparts",
59+
"title": "exportparts",
60+
"heading": "",
61+
"text": "The exportparts global attribute allows you to select and style elements existing in nested shadow trees, by exporting their part names.",
62+
"docType": {
63+
"type": "MDN",
64+
"tag": "other"
65+
},
66+
"isExternal": true
67+
},
68+
{
69+
"relativeUrl": "https://www.npmjs.com/package/postcss-lit",
70+
"title": "postcss-lit",
71+
"heading": "",
72+
"text": "A PostCSS and stylelint custom syntax for parsing CSS inside lit templates. You must specify all tailwind directives you intend to use in your CSS, otherwise their replacement CSS will be incorrectly appended to the end of the document. For example, in the code above, @tailwind base and @tailwind utilities were specified to make text-xs available. Without them, the code would not build.",
73+
"docType": {
74+
"type": "npm",
75+
"tag": "other"
76+
},
77+
"isExternal": true
5678
}
5779
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @typedef {{
3+
* urls: !Array<string>,
4+
* keywords: !Array<string>
5+
* }} KeywordRecord
6+
*
7+
* @typedef {{
8+
* keywords: !Array<!KeywordRecord>
9+
* }} KeywordModifiers
10+
*/
11+
12+
/** @type {KeywordModifiers} */
13+
const keywords = {
14+
keywords: [
15+
{
16+
keywords: ['componentDidMount', 'onMounted', 'ngOnInit'],
17+
urls: [
18+
'/docs/components/lifecycle/#connectedcallback',
19+
'/articles/lit-cheat-sheet/#connectedcallback'
20+
]
21+
},
22+
{
23+
keywords: [
24+
'ngAfterViewInit',
25+
'afterFirstRender'
26+
],
27+
urls: [
28+
'/docs/components/lifecycle/#firstupdated',
29+
'/articles/lit-cheat-sheet/#firstupdated'
30+
]
31+
},
32+
{
33+
keywords: [
34+
'componentDidUpdate',
35+
'afterUpdate'
36+
],
37+
urls: [
38+
'/docs/components/lifecycle/#updated',
39+
'/articles/lit-cheat-sheet/#updated'
40+
]
41+
},
42+
{
43+
keywords: [
44+
'shouldComponentUpdate',
45+
'updateCheck'
46+
],
47+
urls: [
48+
'/docs/components/lifecycle/#shouldupdate'
49+
]
50+
},
51+
{
52+
keywords: [
53+
'componentWillUnmount',
54+
'onUnmounted',
55+
'ngOnDestroy'
56+
],
57+
urls: [
58+
'/docs/components/lifecycle/#disconnectedcallback',
59+
'/articles/lit-cheat-sheet/#disconnectedcallback'
60+
]
61+
},
62+
{
63+
keywords: [
64+
'renderToPipeableStream',
65+
'renderToReadableStream',
66+
'renderToStaticMarkup',
67+
'renderToString'
68+
],
69+
urls: [
70+
'/docs/ssr/server-usage/#handling-renderresults'
71+
]
72+
},
73+
{
74+
keywords: ['tsx'],
75+
urls: [
76+
'/docs/frameworks/react/#createcomponent',
77+
'https://www.youtube.com/watch?v=agBn1LW6dbM',
78+
]
79+
},
80+
{
81+
keywords: ['exportparts'],
82+
urls: [
83+
'https://www.youtube.com/watch?v=Xt7blcyuw5s',
84+
]
85+
}
86+
]
87+
};
88+
89+
/**
90+
* 11ty data JS loader.
91+
*
92+
* @returns {!KeywordModifiers} The keywords data for 11ty search index rendered in search-modifiers/keywords.
93+
*/
94+
module.exports = async () => {
95+
return keywords;
96+
};
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
permalink: external-search-data/data.json
2+
permalink: search-modifiers/external-data.json
33
---
44

55
{% if not env.DEV %}{{ externalSearchData | dump | safe }}{% endif %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
permalink: search-modifiers/keywords.json
3+
---
4+
5+
{% if not env.DEV %}
6+
{{ searchKeywords | dump | safe }}
7+
{% endif %}

packages/lit-dev-content/site/external-search-data/videos.html packages/lit-dev-content/site/search-modifiers/videos.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
permalink: external-search-data/videos.json
2+
permalink: search-modifiers/videos.json
33
---
44

55
{% if not env.DEV %}

packages/lit-dev-tools-cjs/src/search/indexers/index-external-data.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export async function indexExternalData(
1919
// Path of the external data index.
2020
const EXTERNAL_DATA_INDEX_PATH = path.resolve(
2121
__dirname,
22-
`../../../../lit-dev-content/${outputDir}/external-search-data/data.json`
22+
`../../../../lit-dev-content/${outputDir}/search-modifiers/external-data.json`
2323
);
2424

2525
const fileContents = await fs.readFile(EXTERNAL_DATA_INDEX_PATH, 'utf-8');

packages/lit-dev-tools-cjs/src/search/indexers/index-videos.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export async function indexVideos(outputDir: '_dev' | '_site', idOffset = 0) {
1616
// Path of the video index.
1717
const VIDEO_INDEX_PATH = path.resolve(
1818
__dirname,
19-
`../../../../lit-dev-content/${outputDir}/external-search-data/videos.json`
19+
`../../../../lit-dev-content/${outputDir}/search-modifiers/videos.json`
2020
);
2121

2222
const fileContents = await fs.readFile(VIDEO_INDEX_PATH, 'utf-8');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
*/
6+
7+
import * as fs from 'fs/promises';
8+
import * as path from 'path';
9+
import type {UserFacingPageData} from '../plugin';
10+
11+
interface KeywordRecord {
12+
urls: string[];
13+
keywords: string[];
14+
}
15+
16+
interface KeywordModifiers {
17+
keywords: KeywordRecord[];
18+
}
19+
20+
/**
21+
* Adds keyword metadata to pages in the search index based on keyword modifiers defined in a JSON file.
22+
* Only processes keywords for production builds (when outputDir is '_site').
23+
*
24+
* @param outputDir - The output directory for the build ('_dev' or '_site'). Keywords are only added for '_site' builds.
25+
* @param index - Array of page data objects to be enhanced with keywords
26+
* @returns The modified index array with keywords added to relevant pages. Returns empty array for dev builds.
27+
*/
28+
export async function addKeywords(
29+
outputDir: '_dev' | '_site',
30+
index: UserFacingPageData[]
31+
) {
32+
if (outputDir === '_dev') {
33+
return index;
34+
}
35+
36+
// Path to the keyword modifiers JSON file.
37+
const KEYWORD_MODIFIERS_PATH = path.resolve(
38+
__dirname,
39+
`../../../../lit-dev-content/${outputDir}/search-modifiers/keywords.json`
40+
);
41+
42+
const fileContents = await fs.readFile(KEYWORD_MODIFIERS_PATH, 'utf-8');
43+
const data = JSON.parse(fileContents) as KeywordModifiers;
44+
45+
const keywordMap = new Map<string, Set<string>>();
46+
47+
// Create a map of urls to keywords associated with that url.
48+
for (const keywordRecord of data.keywords) {
49+
const keywords = new Set(keywordRecord.keywords);
50+
51+
for (const url of keywordRecord.urls) {
52+
let keywordsForURL = keywordMap.get(url);
53+
if (!keywordsForURL) {
54+
keywordsForURL = new Set<string>();
55+
keywordMap.set(url, keywordsForURL);
56+
}
57+
58+
for (const keyword of keywords) {
59+
keywordsForURL.add(keyword);
60+
}
61+
}
62+
}
63+
64+
// Add keywords to the index to each url that has keywords associated with it.
65+
for (const page of index) {
66+
const keywords = keywordMap.get(page.relativeUrl);
67+
if (keywords) {
68+
page.keywords = Array.from(keywords);
69+
}
70+
}
71+
72+
return index;
73+
}

packages/lit-dev-tools-cjs/src/search/plugin.ts

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
indexVideos,
1414
indexExternalData,
1515
} from './indexers/index.js';
16+
import {addKeywords} from './indexers/keywords.js';
1617

1718
/**
1819
* Generic that describes the type of document.
@@ -46,6 +47,7 @@ export interface UserFacingPageData {
4647
parentID?: string;
4748
isExternal?: boolean;
4849
docType: DocTypes;
50+
keywords?: string[];
4951
}
5052

5153
/**
@@ -92,5 +94,7 @@ export async function createSearchIndex(outputDir: '_dev' | '_site') {
9294
...externalSearchData,
9395
];
9496

97+
await addKeywords(outputDir, searchIndex);
98+
9599
fs.writeFileSync(OUT_PATH, JSON.stringify(searchIndex));
96100
}

0 commit comments

Comments
 (0)